Всем привет! Собственно сабж. Интересует решение для Windows 10/11 x64. Экспериментирую следующим образом: В DriverEntry() (IRQL == PASSIVE_LEVEL): 1) Создаю тестовый поток, он начинает крутится в цикле (jmp $), адрес инструкции где он крутится мне в дальнейшем известен. Указатель на его KTHREAD так же известен 2) Делаю задержку 3 сек. Чтобы поток точно успел дойти до jmp $ 3) Ставлю DPC в очередь В одном из DPC-хэндлеров (остальные ждут пока он отработает) (IRQL == DISPATCH_LEVEL): 1) Посредством PsGetCurrentProcess() получаем указатель на KPROCESS процесса в контексте которого мы находимся 2) Берем поле KPROCESS.ProcessListEntry и проходимся по этом списку, т.о. перечисляем все процессы 3) В каждом найденном KPROCESS берем поле KPROCESS.ThreadListHead и проходимся по этому списку, т.о. перечисляем все потоки процесса 4) Проверяем поле KTHREAD.TrapFrame, если оно неравно NULL значит поток отбрасываем. Он был прерван из юзер-мода и нас не интересует 5) Проверяем поле KTHREAD.State, если не Ready значит тоже отбрасываем 6) Сравниваю указатель на KTHREAD с сохраненным в DriverEntry(), т.о. находим тестовый поток (который крутится на jmp $) В вот дальше заминка. Нужно найти KTRAP_FRAME в стеке и взять оттуда RIP. Пробовал всяко. Скармливал указатель на KTHREAD функции PsGetBaseTrapFrame(). Вернувшийся указатель указывал в стек но явно не на фрэйм. В регистрах чепуха, в т.ч. и в RIP. Пробовал пройтись от KTHREAD.InitialStack до KTHREAD.StackLimit с вычитом по 8 байт. RIP в стеке нашел, но опять же. Команда dt nt!_KTRAP_FRAME показала, что это нифига не фрейм. Еще что-то пробовал, уже и не упомню. В общем прошу помощи!
А откуда она его взяла? Адрес, который она тебе вернула, где-то в стеке потока? По KTHREAD.InitialStack - sizeof(_KTRAP_FRAME) лежит не _KTRAP_FRAME?
Да, она вернула адрес указывающий в стек потока. На скрине её реализация: Это первое, что я попробовал. Увы, там его нет. ================================================= В общем, вот лог из отладчика: Первый указатель на фрэйм получен по формуле KTHREAD.InitialStack - sizeof(KTRAP_FRAME). Проверяем его: Видим, что RIP не совпадает. Да и видно, что остальные поля не валидны. Например, сегментные регистры обнулены. Второй указатель на фрэйм получен посредством PsGetBaseTrapFrame(). Он совпадает с первым. Третий указатель получен по след. алгоритму: Идем от KTHREAD.InitialStack до KTHREAD.StackLimit с вычитом по 8 байт. Если натыкаемся на наш RIP, вычитаем из найденного адреса смещение на KTRAP_FRAME.Rip. Проверяем его: Опять же, видно, что данные невалидны. Rip, понятное дело, совпадает. --- Сообщение объединено, 25 окт 2022 --- Приложу, на всякий случай, код. Но заранее извиняюсь. Код черновой. Код (C++): #include "Common.hpp" #include "System.hpp" #include "Callbacks.hpp" #include "Intrinsics.hpp" extern "C" VOID InfiniteLoop(); const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x240ULL; const ULONGLONG KPROCESS_THREAD_LIST_HEAD_OFFSET = 0x030ULL; const ULONGLONG KTHREAD_THREAD_LIST_ENTRY_OFFSET = 0x2f8ULL; const ULONGLONG KTHREAD_TRAP_FRAME_OFFSET = 0x090ULL; const ULONGLONG KTHREAD_INITIAL_STACK_OFFSET = 0x028ULL; const ULONGLONG KTHREAD_STATE_OFFSET = 0x184ULL; const ULONGLONG KTRAP_FRAME_RIP_OFFSET = 0x168ULL; const ULONGLONG KTHREAD_STACK_LIMIT_OFFSET = 0x030ULL; namespace { // anonymous namespace typedef enum _KTHREAD_STATE { Initialized, Ready, Running, Standby, Terminated, Waiting, Transition, DeferredReady, // [FIXME] ??? GateWaitObsolete, WaitingForProcessInSwap } KTHREAD_STATE, *PKTHREAD_STATE; void InfiniteLoopThread(PVOID Arg) { InfiniteLoop(); PBOOLEAN pbThreadTerminate = (PBOOLEAN)Arg; (*pbThreadTerminate) = TRUE; NTSTATUS NtStatus = PsTerminateSystemThread(STATUS_SUCCESS); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("PsTerminateSystemThread", NtStatus); } } volatile LONG g_bAcquire = FALSE; struct DpcRoutineParams { ULONGLONG pKrnImage; ULONGLONG pKrnImageLastByte; ULONGLONG pInfiniteThread; }; void DpcRoutine(PVOID Arg) { if (InterlockedExchange(&g_bAcquire, TRUE)) return; DpcRoutineParams* pParams = (DpcRoutineParams*)Arg; const PLIST_ENTRY pProcessListEntry = (PLIST_ENTRY)((ULONGLONG) PsGetCurrentProcess() + KPROCESS_PROCESS_LIST_ENTRY_OFFSET); PLIST_ENTRY pEntry = pProcessListEntry; typedef ULONGLONG /* pKTrapFrame */ (*pPsGetBaseTrapFrame)(ULONGLONG pKProcess); pPsGetBaseTrapFrame PsGetBaseTrapFrame = (pPsGetBaseTrapFrame)(pParams->pKrnImage + 0x023021CULL); do { if (((ULONGLONG)pEntry < pParams->pKrnImage) || ((ULONGLONG)pEntry > pParams->pKrnImageLastByte)) { const ULONGLONG pKProcess = (ULONGLONG)pEntry - KPROCESS_PROCESS_LIST_ENTRY_OFFSET; const PLIST_ENTRY pThreadListHead = (PLIST_ENTRY)(pKProcess + KPROCESS_THREAD_LIST_HEAD_OFFSET); for (PLIST_ENTRY pEntry = pThreadListHead->Flink; pEntry != pThreadListHead; pEntry = pEntry->Flink) { const ULONGLONG pKThread = (ULONGLONG)pEntry - KTHREAD_THREAD_LIST_ENTRY_OFFSET; const BYTE ThreadState = *((PBYTE)(pKThread + KTHREAD_STATE_OFFSET)); if ((NULL == *((PULONGLONG)(pKThread + KTHREAD_TRAP_FRAME_OFFSET))) && ((BYTE)Ready == ThreadState)) { //const PULONGLONG pRip = (PULONGLONG)(pKTrapFrame + //KTRAP_FRAME_RIP_OFFSET); // [DEL] BEGIN if (pKThread == pParams->pInfiniteThread) { LOG_INF("RIP: 0x%p", (ULONGLONG)InfiniteLoop); const ULONGLONG pKTrapFrame1 = (*((PULONGLONG)(pKThread + KTHREAD_INITIAL_STACK_OFFSET))) - 0x190ULL; const ULONGLONG pKTrapFrame2 = PsGetBaseTrapFrame(pKThread); ULONGLONG pStack = *((PULONGLONG)(pKThread + KTHREAD_INITIAL_STACK_OFFSET)); ULONGLONG pStackLimit = *((PULONGLONG)(pKThread + KTHREAD_STACK_LIMIT_OFFSET)); for (; pStack != pStackLimit; pStack = pStack - 8ULL) { if ((*((PULONGLONG)pStack)) == (ULONGLONG)InfiniteLoop) { //LOG_INF("Rip found, pStack: 0x%p", pStack); break; } } const ULONGLONG pKTrapFrame3 = pStack - KTRAP_FRAME_RIP_OFFSET; LOG_INF("pKTrapFrame1: 0x%p", pKTrapFrame1); LOG_INF("pKTrapFrame2: 0x%p", pKTrapFrame2); LOG_INF("pKTrapFrame3: 0x%p", pKTrapFrame3); __debugbreak(); } // [DEL] END /*if ((ULONGLONG)InfiniteLoop == (*pRip)) { LOG_INF("Infinite loop detected"); (*pRip) = (*pRip) + 2ULL; }*/ } } } pEntry = pEntry->Flink; } while (pEntry != pProcessListEntry); } }; // anonymous namespace NTSTATUS FixContextPoC() { BOOLEAN bThreadTerminate = FALSE; PETHREAD pEThread = NULL; HANDLE hThread; NTSTATUS NtStatus = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, InfiniteLoopThread, &bThreadTerminate); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("PsCreateSystemThread", NtStatus); goto Error; } NtStatus = ObReferenceObjectByHandle(hThread, SYNCHRONIZE, *PsThreadType, KernelMode, (PVOID*)&pEThread, NULL); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("ObReferenceObjectByHandle", NtStatus); ZwClose(hThread); goto Error; } ZwClose(hThread); SLEEP(3000); ULONGLONG pKrnImage = 0; DWORD KrnImageSize = 0; NtStatus = Sys::GetNtoskrnlBase(&pKrnImage, &KrnImageSize); if (!NT_SUCCESS(NtStatus)) { goto Error; } DpcRoutineParams Params; Params.pKrnImage = pKrnImage; Params.pKrnImageLastByte = pKrnImage + KrnImageSize - 1ULL; Params.pInfiniteThread = (ULONGLONG)pEThread; Callbacks::CallDpcForEachCpu(DpcRoutine, &Params); SLEEP(3000); if (bThreadTerminate) { LOG_INF("Test finished successful"); } else { LOG_WAR("Test finished unsuccessful"); } ObDereferenceObject(pEThread); return STATUS_SUCCESS; Error: if (NULL != pEThread) ObDereferenceObject(pEThread); return NtStatus; } --- Сообщение объединено, 25 окт 2022 --- Касательно того, что я сравниваю с адресом InfiniteLoop. Здесь ошибки нет. Проверял в IDA. Указывает прямо на jmp $. Инкрементная линковка для проекта отключена. Код (ASM): InfiniteLoop proc M1: jmp M1 ret InfiniteLoop endp --- Сообщение объединено, 25 окт 2022 --- Кстати, на счет указателя на RIP, который я нашел третьим способом (KTHREAD.InitialStack до KTHREAD.StackLimit с вычитом по 8 байт). Он действующий. Если его пропатчить (RIP += 2), то поток выходит из мертвой петли. Работает. Но, все-таки, странно. Почему RIP не лежит внутри KTRAP_FRAME? Видимо, на современных версиях ОС, данный механизм изменился. Как вариант, можно найти указатель на RIP на разных версиях и зашить в программу смещения относительно KTHREAD.InitialStack. Но не хочется. Хотелось бы понять как оно работает. --- Сообщение объединено, 25 окт 2022 --- Хм..., а можно и не зашивать смещение. Получать оффсет в динамике по третьему способу. Правда, мудрено получается.
В общем, разобрался. Я ошибался. RIP все таки лежит в KTRAP_FRAME. Некоторые поля не используются это и ввело в заблуждение. Когда поток прерывается система берет текущий RSP, ровняет его на 0x16 байт и вычитает sizeof(KTRAP_FRAME). Получается значение RSP связано с указателем на KTPAP_FRAME. RSP и будем искать. Для надежности, когда найдем фрэйм, можно еще проверить KTRAP_FRAME.EFlags.IF. Алгоритм: 1) Идем от KTHREAD.InitialStack до KTHREAD.KernelStack с вычитом по 8 байт (фрэйм всегда в данном диапазоне) 2) Берем значение из стека, назовем его Value, если это не указатель в KTHREAD.InitialStack : KTHREAD.KernelStack, то goto 1 3) Если выражение (pValue == ALIGN_DOWN_BY(Value, 0x10) - sizeof(KTRAP_FRAME) + OFFSET_OF(KTRAP_FRAME, Rsp)) ложно, то goto 1 4) Если (KTRAP_FRAME.EFlags.IF == FALSE), то goto 1 Черновой код: Код (C++): #include "Common.hpp" #include "System.hpp" #include "Callbacks.hpp" #include "Intrinsics.hpp" #include "Cpu.hpp" extern "C" VOID InfiniteLoop(ULONGLONG Arg1); //const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x240ULL; // Threshold const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x350ULL; // 20H2/21H2 const ULONGLONG KPROCESS_THREAD_LIST_HEAD_OFFSET = 0x030ULL; const ULONGLONG KTHREAD_THREAD_LIST_ENTRY_OFFSET = 0x2f8ULL; const ULONGLONG KTHREAD_TRAP_FRAME_OFFSET = 0x090ULL; const ULONGLONG KTHREAD_INITIAL_STACK_OFFSET = 0x028ULL; const ULONGLONG KTHREAD_STATE_OFFSET = 0x184ULL; const ULONGLONG KTHREAD_KERNEL_STACK_OFFSET = 0x058ULL; const ULONGLONG KTRAP_FRAME_RIP_OFFSET = 0x168ULL; const ULONGLONG KTRAP_FRAME_RSP_OFFSET = 0x180ULL; const ULONGLONG KTRAP_FRAME_EFLAGS_OFFSET = 0x178ULL; const ULONGLONG KTRAP_FRAME_SIZE = 0x190ULL; #define ALIGN_DOWN_BY(length, alignment) \ ((ULONG_PTR)(length) & ~(alignment - 1)) namespace { // anonymous namespace typedef enum _KTHREAD_STATE { Initialized, Ready, Running, Standby, Terminated, Waiting, Transition, DeferredReady, GateWaitObsolete, WaitingForProcessInSwap } KTHREAD_STATE, *PKTHREAD_STATE; void InfiniteLoopThread(PVOID Arg) { InfiniteLoop(0x1122334455667788ULL); PBOOLEAN pbThreadTerminate = (PBOOLEAN)Arg; (*pbThreadTerminate) = TRUE; NTSTATUS NtStatus = PsTerminateSystemThread(STATUS_SUCCESS); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("PsTerminateSystemThread", NtStatus); } } volatile LONG g_bAcquire = FALSE; struct DpcRoutineParams { ULONGLONG pKrnImage; ULONGLONG pKrnImageLastByte; ULONGLONG pInfiniteThread; }; void DpcRoutine(PVOID Arg) { if (InterlockedExchange(&g_bAcquire, TRUE)) return; DpcRoutineParams* pParams = (DpcRoutineParams*)Arg; const PLIST_ENTRY pProcessListEntry = (PLIST_ENTRY)((ULONGLONG) PsGetCurrentProcess() + KPROCESS_PROCESS_LIST_ENTRY_OFFSET); PLIST_ENTRY pEntry = pProcessListEntry; do { if (((ULONGLONG)pEntry < pParams->pKrnImage) || ((ULONGLONG)pEntry > pParams->pKrnImageLastByte)) { const ULONGLONG pKProcess = (ULONGLONG)pEntry - KPROCESS_PROCESS_LIST_ENTRY_OFFSET; const PLIST_ENTRY pThreadListHead = (PLIST_ENTRY)(pKProcess + KPROCESS_THREAD_LIST_HEAD_OFFSET); for (PLIST_ENTRY pEntry = pThreadListHead->Flink; pEntry != pThreadListHead; pEntry = pEntry->Flink) { const ULONGLONG pKThread = (ULONGLONG)pEntry - KTHREAD_THREAD_LIST_ENTRY_OFFSET; const BYTE ThreadState = *((PBYTE)(pKThread + KTHREAD_STATE_OFFSET)); if ((NULL == *((PULONGLONG)(pKThread + KTHREAD_TRAP_FRAME_OFFSET))) && (((BYTE)Ready == ThreadState) || ((BYTE)Running == ThreadState) || ((BYTE)Standby == ThreadState)) && (pParams->pInfiniteThread == pKThread)) { LOG_INF("Infinite thread detected"); const ULONGLONG pInitialStack = *((PULONGLONG)(pKThread + KTHREAD_INITIAL_STACK_OFFSET)); const ULONGLONG pKernelStack = *((PULONGLONG)(pKThread + KTHREAD_KERNEL_STACK_OFFSET)); PULONGLONG pRip = NULL; for (ULONGLONG pStack = pInitialStack - 8ULL; pStack != pKernelStack; pStack = pStack - 8ULL) { if (!((pStack >= pKernelStack) && (pStack <= pInitialStack))) continue; const ULONGLONG pKTrapFrame = ALIGN_DOWN_BY( *((PULONGLONG)pStack), 0x10ULL) - KTRAP_FRAME_SIZE; if (!(pStack == pKTrapFrame + KTRAP_FRAME_RSP_OFFSET)) continue; Cpu::EFLAGS EFlags = { *((PULONG)(pKTrapFrame + KTRAP_FRAME_EFLAGS_OFFSET)) }; if (!(EFlags.Bitmap.IF)) continue; pRip = (PULONGLONG)(pKTrapFrame + KTRAP_FRAME_RIP_OFFSET); break; } ASSERT(NULL != pRip); (*pRip) = (*pRip) + 2ULL; } } } pEntry = pEntry->Flink; } while (pEntry != pProcessListEntry); } }; // anonymous namespace NTSTATUS FixContextPoC() { BOOLEAN bThreadTerminate = FALSE; PETHREAD pEThread = NULL; HANDLE hThread; NTSTATUS NtStatus = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, InfiniteLoopThread, &bThreadTerminate); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("PsCreateSystemThread", NtStatus); goto Error; } NtStatus = ObReferenceObjectByHandle(hThread, SYNCHRONIZE, *PsThreadType, KernelMode, (PVOID*)&pEThread, NULL); if (!NT_SUCCESS(NtStatus)) { LOG_CODE("ObReferenceObjectByHandle", NtStatus); ZwClose(hThread); goto Error; } LOG_INF("pKThread: 0x%p", pEThread); ZwClose(hThread); SLEEP(3000); ULONGLONG pKrnImage = 0; DWORD KrnImageSize = 0; NtStatus = Sys::GetNtoskrnlBase(&pKrnImage, &KrnImageSize); if (!NT_SUCCESS(NtStatus)) { goto Error; } DpcRoutineParams Params; Params.pKrnImage = pKrnImage; Params.pKrnImageLastByte = pKrnImage + KrnImageSize - 1ULL; Params.pInfiniteThread = (ULONGLONG)pEThread; Callbacks::CallDpcForEachCpu(DpcRoutine, &Params); SLEEP(3000); if (bThreadTerminate) { LOG_INF("Test finished successful"); } else { LOG_WAR("Test finished unsuccessful"); } ObDereferenceObject(pEThread); return STATUS_SUCCESS; Error: if (NULL != pEThread) ObDereferenceObject(pEThread); return NtStatus; } Протестировано на Windows 10 Threshold/20H2 и Windows 11 21H2.