Получить RIP прерванного ядерного потока из DPC-хэндлера

Тема в разделе "WASM.NT.KERNEL", создана пользователем Andrey333, 24 окт 2022.

  1. Andrey333

    Andrey333 Member

    Публикаций:
    0
    Регистрация:
    30 янв 2020
    Сообщения:
    35
    Всем привет!

    Собственно сабж. Интересует решение для 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 показала, что это нифига не фрейм.

    Еще что-то пробовал, уже и не упомню.

    В общем прошу помощи!
     
  2. HoShiMin

    HoShiMin Well-Known Member

    Публикаций:
    5
    Регистрация:
    17 дек 2016
    Сообщения:
    1.455
    Адрес:
    Россия, Нижний Новгород
    А откуда она его взяла? Адрес, который она тебе вернула, где-то в стеке потока? По KTHREAD.InitialStack - sizeof(_KTRAP_FRAME) лежит не _KTRAP_FRAME?
     
  3. Andrey333

    Andrey333 Member

    Публикаций:
    0
    Регистрация:
    30 янв 2020
    Сообщения:
    35
    Да, она вернула адрес указывающий в стек потока. На скрине её реализация:
    [​IMG]
    Это первое, что я попробовал. Увы, там его нет.

    =================================================

    В общем, вот лог из отладчика:

    Первый указатель на фрэйм получен по формуле KTHREAD.InitialStack - sizeof(KTRAP_FRAME). Проверяем его:

    Видим, что RIP не совпадает. Да и видно, что остальные поля не валидны. Например, сегментные регистры обнулены.

    Второй указатель на фрэйм получен посредством PsGetBaseTrapFrame(). Он совпадает с первым.

    Третий указатель получен по след. алгоритму: Идем от KTHREAD.InitialStack до KTHREAD.StackLimit с вычитом по 8 байт. Если натыкаемся на наш RIP, вычитаем из найденного адреса смещение на
    KTRAP_FRAME.Rip. Проверяем его:

    Опять же, видно, что данные невалидны. Rip, понятное дело, совпадает.
    --- Сообщение объединено, 25 окт 2022 ---
    Приложу, на всякий случай, код. Но заранее извиняюсь. Код черновой.

    Код (C++):
    1.  
    2. #include "Common.hpp"
    3.  
    4. #include "System.hpp"
    5. #include "Callbacks.hpp"
    6. #include "Intrinsics.hpp"
    7.  
    8. extern "C" VOID InfiniteLoop();
    9.  
    10. const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x240ULL;
    11. const ULONGLONG KPROCESS_THREAD_LIST_HEAD_OFFSET = 0x030ULL;
    12. const ULONGLONG KTHREAD_THREAD_LIST_ENTRY_OFFSET = 0x2f8ULL;
    13. const ULONGLONG KTHREAD_TRAP_FRAME_OFFSET = 0x090ULL;
    14. const ULONGLONG KTHREAD_INITIAL_STACK_OFFSET = 0x028ULL;
    15. const ULONGLONG KTHREAD_STATE_OFFSET = 0x184ULL;
    16. const ULONGLONG KTRAP_FRAME_RIP_OFFSET = 0x168ULL;
    17. const ULONGLONG KTHREAD_STACK_LIMIT_OFFSET = 0x030ULL;
    18.  
    19. namespace { // anonymous namespace
    20.  
    21. typedef enum _KTHREAD_STATE {
    22.  
    23.     Initialized,
    24.     Ready,
    25.     Running,  
    26.     Standby,  
    27.     Terminated,  
    28.     Waiting,  
    29.     Transition,
    30.     DeferredReady,              // [FIXME] ???
    31.     GateWaitObsolete,
    32.     WaitingForProcessInSwap
    33.  
    34. } KTHREAD_STATE, *PKTHREAD_STATE;
    35.  
    36. void InfiniteLoopThread(PVOID Arg)
    37. {
    38.     InfiniteLoop();
    39.  
    40.     PBOOLEAN pbThreadTerminate = (PBOOLEAN)Arg;
    41.  
    42.     (*pbThreadTerminate) = TRUE;
    43.  
    44.     NTSTATUS NtStatus = PsTerminateSystemThread(STATUS_SUCCESS);
    45.     if (!NT_SUCCESS(NtStatus)) {
    46.         LOG_CODE("PsTerminateSystemThread", NtStatus);
    47.     }
    48. }
    49.  
    50. volatile LONG g_bAcquire = FALSE;
    51.  
    52. struct DpcRoutineParams {
    53.  
    54.     ULONGLONG pKrnImage;
    55.     ULONGLONG pKrnImageLastByte;
    56.     ULONGLONG pInfiniteThread;
    57. };
    58.  
    59. void DpcRoutine(PVOID Arg)
    60. {  
    61.     if (InterlockedExchange(&g_bAcquire, TRUE)) return;
    62.  
    63.     DpcRoutineParams* pParams = (DpcRoutineParams*)Arg;
    64.  
    65.     const PLIST_ENTRY pProcessListEntry = (PLIST_ENTRY)((ULONGLONG)
    66.         PsGetCurrentProcess() + KPROCESS_PROCESS_LIST_ENTRY_OFFSET);
    67.  
    68.     PLIST_ENTRY pEntry = pProcessListEntry;
    69.  
    70.     typedef ULONGLONG /* pKTrapFrame */ (*pPsGetBaseTrapFrame)(ULONGLONG pKProcess);
    71.  
    72.     pPsGetBaseTrapFrame PsGetBaseTrapFrame = (pPsGetBaseTrapFrame)(pParams->pKrnImage +
    73.         0x023021CULL);
    74.  
    75.     do {
    76.  
    77.         if (((ULONGLONG)pEntry < pParams->pKrnImage) ||
    78.             ((ULONGLONG)pEntry > pParams->pKrnImageLastByte))
    79.         {
    80.             const ULONGLONG pKProcess = (ULONGLONG)pEntry -
    81.                 KPROCESS_PROCESS_LIST_ENTRY_OFFSET;
    82.  
    83.             const PLIST_ENTRY pThreadListHead = (PLIST_ENTRY)(pKProcess +
    84.                 KPROCESS_THREAD_LIST_HEAD_OFFSET);
    85.  
    86.             for (PLIST_ENTRY pEntry = pThreadListHead->Flink;
    87.                 pEntry != pThreadListHead; pEntry = pEntry->Flink)
    88.             {
    89.                 const ULONGLONG pKThread = (ULONGLONG)pEntry -
    90.                     KTHREAD_THREAD_LIST_ENTRY_OFFSET;
    91.  
    92.                 const BYTE ThreadState = *((PBYTE)(pKThread +
    93.                     KTHREAD_STATE_OFFSET));
    94.  
    95.                 if ((NULL == *((PULONGLONG)(pKThread + KTHREAD_TRAP_FRAME_OFFSET))) &&
    96.                     ((BYTE)Ready == ThreadState))
    97.                 {
    98.                     //const PULONGLONG pRip = (PULONGLONG)(pKTrapFrame +
    99.                         //KTRAP_FRAME_RIP_OFFSET);
    100.  
    101.                     // [DEL] BEGIN
    102.  
    103.                     if (pKThread == pParams->pInfiniteThread) {
    104.  
    105.                         LOG_INF("RIP: 0x%p", (ULONGLONG)InfiniteLoop);
    106.  
    107.                         const ULONGLONG pKTrapFrame1 = (*((PULONGLONG)(pKThread +
    108.                             KTHREAD_INITIAL_STACK_OFFSET))) - 0x190ULL;
    109.  
    110.                         const ULONGLONG pKTrapFrame2 = PsGetBaseTrapFrame(pKThread);
    111.  
    112.                         ULONGLONG pStack = *((PULONGLONG)(pKThread +
    113.                             KTHREAD_INITIAL_STACK_OFFSET));
    114.  
    115.                         ULONGLONG pStackLimit = *((PULONGLONG)(pKThread +
    116.                             KTHREAD_STACK_LIMIT_OFFSET));
    117.  
    118.                         for (; pStack != pStackLimit; pStack = pStack - 8ULL) {
    119.                             if ((*((PULONGLONG)pStack)) == (ULONGLONG)InfiniteLoop) {
    120.                                 //LOG_INF("Rip found, pStack: 0x%p", pStack);
    121.                                 break;
    122.                             }
    123.                         }
    124.  
    125.                         const ULONGLONG pKTrapFrame3 = pStack - KTRAP_FRAME_RIP_OFFSET;
    126.  
    127.                         LOG_INF("pKTrapFrame1: 0x%p", pKTrapFrame1);
    128.                         LOG_INF("pKTrapFrame2: 0x%p", pKTrapFrame2);
    129.                         LOG_INF("pKTrapFrame3: 0x%p", pKTrapFrame3);
    130.                      
    131.                         __debugbreak();
    132.                     }
    133.  
    134.                     // [DEL] END
    135.  
    136.                     /*if ((ULONGLONG)InfiniteLoop == (*pRip)) {
    137.  
    138.                         LOG_INF("Infinite loop detected");
    139.  
    140.                         (*pRip) = (*pRip) + 2ULL;
    141.                     }*/
    142.                 }
    143.             }
    144.         }
    145.  
    146.         pEntry = pEntry->Flink;
    147.  
    148.     } while (pEntry != pProcessListEntry);
    149. }
    150.  
    151. }; // anonymous namespace
    152.  
    153. NTSTATUS FixContextPoC()
    154. {
    155.     BOOLEAN bThreadTerminate = FALSE;
    156.     PETHREAD pEThread = NULL;
    157.  
    158.     HANDLE hThread;
    159.     NTSTATUS NtStatus = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL,
    160.         NULL, NULL, InfiniteLoopThread, &bThreadTerminate);
    161.     if (!NT_SUCCESS(NtStatus)) {
    162.         LOG_CODE("PsCreateSystemThread", NtStatus);
    163.         goto Error;
    164.     }
    165.  
    166.     NtStatus = ObReferenceObjectByHandle(hThread, SYNCHRONIZE, *PsThreadType,
    167.         KernelMode, (PVOID*)&pEThread, NULL);
    168.     if (!NT_SUCCESS(NtStatus)) {
    169.         LOG_CODE("ObReferenceObjectByHandle", NtStatus);
    170.         ZwClose(hThread);
    171.         goto Error;
    172.     }  
    173.  
    174.     ZwClose(hThread);
    175.  
    176.     SLEEP(3000);
    177.  
    178.     ULONGLONG pKrnImage = 0;
    179.     DWORD KrnImageSize = 0;
    180.     NtStatus = Sys::GetNtoskrnlBase(&pKrnImage, &KrnImageSize);
    181.     if (!NT_SUCCESS(NtStatus)) {
    182.         goto Error;
    183.     }
    184.  
    185.     DpcRoutineParams Params;
    186.  
    187.     Params.pKrnImage = pKrnImage;
    188.     Params.pKrnImageLastByte = pKrnImage + KrnImageSize - 1ULL;
    189.     Params.pInfiniteThread = (ULONGLONG)pEThread;
    190.  
    191.     Callbacks::CallDpcForEachCpu(DpcRoutine, &Params);
    192.  
    193.     SLEEP(3000);
    194.  
    195.     if (bThreadTerminate) {
    196.  
    197.         LOG_INF("Test finished successful");
    198.     }
    199.     else {
    200.  
    201.         LOG_WAR("Test finished unsuccessful");
    202.     }  
    203.  
    204.     ObDereferenceObject(pEThread);
    205.  
    206.     return STATUS_SUCCESS;
    207.  
    208. Error:
    209.  
    210.     if (NULL != pEThread) ObDereferenceObject(pEThread);
    211.  
    212.     return NtStatus;
    213. }
    --- Сообщение объединено, 25 окт 2022 ---
    Касательно того, что я сравниваю с адресом InfiniteLoop. Здесь ошибки нет. Проверял в IDA. Указывает прямо на jmp $. Инкрементная линковка для проекта отключена.

    Код (ASM):
    1. InfiniteLoop proc
    2.  
    3. M1: jmp M1
    4.     ret
    5.  
    6. InfiniteLoop endp
    --- Сообщение объединено, 25 окт 2022 ---
    Кстати, на счет указателя на RIP, который я нашел третьим способом (KTHREAD.InitialStack до KTHREAD.StackLimit с вычитом по 8 байт). Он действующий. Если его пропатчить (RIP += 2), то поток выходит из мертвой петли. Работает. Но, все-таки, странно. Почему RIP не лежит внутри KTRAP_FRAME? Видимо, на современных версиях ОС, данный механизм изменился. Как вариант, можно найти указатель на RIP на разных версиях и зашить в программу смещения относительно KTHREAD.InitialStack. Но не хочется. Хотелось бы понять как оно работает.
    --- Сообщение объединено, 25 окт 2022 ---
    Хм..., а можно и не зашивать смещение. Получать оффсет в динамике по третьему способу. Правда, мудрено получается.
     
    Последнее редактирование: 25 окт 2022
  4. Andrey333

    Andrey333 Member

    Публикаций:
    0
    Регистрация:
    30 янв 2020
    Сообщения:
    35
    В общем, разобрался. Я ошибался. 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++):
    1.  
    2. #include "Common.hpp"
    3.  
    4. #include "System.hpp"
    5. #include "Callbacks.hpp"
    6. #include "Intrinsics.hpp"
    7. #include "Cpu.hpp"
    8.  
    9. extern "C" VOID InfiniteLoop(ULONGLONG Arg1);
    10.  
    11. //const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x240ULL; // Threshold
    12. const ULONGLONG KPROCESS_PROCESS_LIST_ENTRY_OFFSET = 0x350ULL; // 20H2/21H2
    13. const ULONGLONG KPROCESS_THREAD_LIST_HEAD_OFFSET = 0x030ULL;
    14. const ULONGLONG KTHREAD_THREAD_LIST_ENTRY_OFFSET = 0x2f8ULL;
    15. const ULONGLONG KTHREAD_TRAP_FRAME_OFFSET = 0x090ULL;
    16. const ULONGLONG KTHREAD_INITIAL_STACK_OFFSET = 0x028ULL;
    17. const ULONGLONG KTHREAD_STATE_OFFSET = 0x184ULL;
    18. const ULONGLONG KTHREAD_KERNEL_STACK_OFFSET = 0x058ULL;
    19. const ULONGLONG KTRAP_FRAME_RIP_OFFSET = 0x168ULL;
    20. const ULONGLONG KTRAP_FRAME_RSP_OFFSET = 0x180ULL;
    21. const ULONGLONG KTRAP_FRAME_EFLAGS_OFFSET = 0x178ULL;
    22.  
    23. const ULONGLONG KTRAP_FRAME_SIZE = 0x190ULL;
    24.  
    25. #define ALIGN_DOWN_BY(length, alignment) \
    26.     ((ULONG_PTR)(length) & ~(alignment - 1))
    27.  
    28. namespace { // anonymous namespace
    29.  
    30. typedef enum _KTHREAD_STATE {
    31.  
    32.     Initialized,
    33.     Ready,
    34.     Running,  
    35.     Standby,  
    36.     Terminated,  
    37.     Waiting,  
    38.     Transition,
    39.     DeferredReady,
    40.     GateWaitObsolete,
    41.     WaitingForProcessInSwap
    42.  
    43. } KTHREAD_STATE, *PKTHREAD_STATE;
    44.  
    45. void InfiniteLoopThread(PVOID Arg)
    46. {
    47.     InfiniteLoop(0x1122334455667788ULL);
    48.  
    49.     PBOOLEAN pbThreadTerminate = (PBOOLEAN)Arg;
    50.  
    51.     (*pbThreadTerminate) = TRUE;
    52.  
    53.     NTSTATUS NtStatus = PsTerminateSystemThread(STATUS_SUCCESS);
    54.     if (!NT_SUCCESS(NtStatus)) {
    55.         LOG_CODE("PsTerminateSystemThread", NtStatus);
    56.     }
    57. }
    58.  
    59. volatile LONG g_bAcquire = FALSE;
    60.  
    61. struct DpcRoutineParams {
    62.  
    63.     ULONGLONG pKrnImage;
    64.     ULONGLONG pKrnImageLastByte;
    65.     ULONGLONG pInfiniteThread;
    66. };
    67.  
    68. void DpcRoutine(PVOID Arg)
    69. {  
    70.     if (InterlockedExchange(&g_bAcquire, TRUE)) return;
    71.  
    72.     DpcRoutineParams* pParams = (DpcRoutineParams*)Arg;
    73.  
    74.     const PLIST_ENTRY pProcessListEntry = (PLIST_ENTRY)((ULONGLONG)
    75.         PsGetCurrentProcess() + KPROCESS_PROCESS_LIST_ENTRY_OFFSET);
    76.  
    77.     PLIST_ENTRY pEntry = pProcessListEntry;
    78.  
    79.     do {
    80.  
    81.         if (((ULONGLONG)pEntry < pParams->pKrnImage) ||
    82.             ((ULONGLONG)pEntry > pParams->pKrnImageLastByte))
    83.         {
    84.             const ULONGLONG pKProcess = (ULONGLONG)pEntry -
    85.                 KPROCESS_PROCESS_LIST_ENTRY_OFFSET;
    86.  
    87.             const PLIST_ENTRY pThreadListHead = (PLIST_ENTRY)(pKProcess +
    88.                 KPROCESS_THREAD_LIST_HEAD_OFFSET);
    89.  
    90.             for (PLIST_ENTRY pEntry = pThreadListHead->Flink;
    91.                 pEntry != pThreadListHead; pEntry = pEntry->Flink)
    92.             {
    93.                 const ULONGLONG pKThread = (ULONGLONG)pEntry -
    94.                     KTHREAD_THREAD_LIST_ENTRY_OFFSET;
    95.  
    96.                 const BYTE ThreadState = *((PBYTE)(pKThread +
    97.                     KTHREAD_STATE_OFFSET));
    98.  
    99.                 if ((NULL == *((PULONGLONG)(pKThread + KTHREAD_TRAP_FRAME_OFFSET))) &&
    100.                     (((BYTE)Ready == ThreadState) || ((BYTE)Running == ThreadState) ||
    101.                         ((BYTE)Standby == ThreadState)) &&
    102.                     (pParams->pInfiniteThread == pKThread))
    103.                 {
    104.                     LOG_INF("Infinite thread detected");
    105.  
    106.                     const ULONGLONG pInitialStack = *((PULONGLONG)(pKThread +
    107.                         KTHREAD_INITIAL_STACK_OFFSET));
    108.  
    109.                     const ULONGLONG pKernelStack = *((PULONGLONG)(pKThread +
    110.                         KTHREAD_KERNEL_STACK_OFFSET));
    111.  
    112.                     PULONGLONG pRip = NULL;
    113.  
    114.                     for (ULONGLONG pStack = pInitialStack - 8ULL;
    115.                         pStack != pKernelStack; pStack = pStack - 8ULL)
    116.                     {
    117.                         if (!((pStack >= pKernelStack) && (pStack <= pInitialStack)))
    118.                             continue;
    119.  
    120.                         const ULONGLONG pKTrapFrame = ALIGN_DOWN_BY(
    121.                             *((PULONGLONG)pStack), 0x10ULL) - KTRAP_FRAME_SIZE;
    122.  
    123.                         if (!(pStack == pKTrapFrame + KTRAP_FRAME_RSP_OFFSET))
    124.                             continue;
    125.  
    126.                         Cpu::EFLAGS EFlags = { *((PULONG)(pKTrapFrame +
    127.                             KTRAP_FRAME_EFLAGS_OFFSET)) };
    128.  
    129.                         if (!(EFlags.Bitmap.IF)) continue;
    130.  
    131.                         pRip = (PULONGLONG)(pKTrapFrame + KTRAP_FRAME_RIP_OFFSET);
    132.                         break;
    133.                     }
    134.  
    135.                     ASSERT(NULL != pRip);
    136.  
    137.                     (*pRip) = (*pRip) + 2ULL;
    138.                 }
    139.             }
    140.         }
    141.  
    142.         pEntry = pEntry->Flink;
    143.  
    144.     } while (pEntry != pProcessListEntry);
    145. }
    146.  
    147. }; // anonymous namespace
    148.  
    149. NTSTATUS FixContextPoC()
    150. {
    151.     BOOLEAN bThreadTerminate = FALSE;
    152.  
    153.     PETHREAD pEThread = NULL;
    154.  
    155.     HANDLE hThread;
    156.     NTSTATUS NtStatus = PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL,
    157.         NULL, NULL, InfiniteLoopThread, &bThreadTerminate);
    158.     if (!NT_SUCCESS(NtStatus)) {
    159.         LOG_CODE("PsCreateSystemThread", NtStatus);
    160.         goto Error;
    161.     }
    162.  
    163.     NtStatus = ObReferenceObjectByHandle(hThread, SYNCHRONIZE, *PsThreadType,
    164.         KernelMode, (PVOID*)&pEThread, NULL);
    165.     if (!NT_SUCCESS(NtStatus)) {
    166.         LOG_CODE("ObReferenceObjectByHandle", NtStatus);
    167.         ZwClose(hThread);
    168.         goto Error;
    169.     }  
    170.  
    171.     LOG_INF("pKThread: 0x%p", pEThread);
    172.  
    173.     ZwClose(hThread);
    174.  
    175.     SLEEP(3000);
    176.  
    177.     ULONGLONG pKrnImage = 0;
    178.     DWORD KrnImageSize = 0;
    179.     NtStatus = Sys::GetNtoskrnlBase(&pKrnImage, &KrnImageSize);
    180.     if (!NT_SUCCESS(NtStatus)) {
    181.         goto Error;
    182.     }
    183.  
    184.     DpcRoutineParams Params;
    185.  
    186.     Params.pKrnImage = pKrnImage;
    187.     Params.pKrnImageLastByte = pKrnImage + KrnImageSize - 1ULL;
    188.     Params.pInfiniteThread = (ULONGLONG)pEThread;
    189.  
    190.     Callbacks::CallDpcForEachCpu(DpcRoutine, &Params);
    191.  
    192.     SLEEP(3000);
    193.  
    194.     if (bThreadTerminate) {
    195.  
    196.         LOG_INF("Test finished successful");
    197.     }
    198.     else {
    199.  
    200.         LOG_WAR("Test finished unsuccessful");
    201.     }  
    202.  
    203.     ObDereferenceObject(pEThread);
    204.  
    205.     return STATUS_SUCCESS;
    206.  
    207. Error:
    208.  
    209.     if (NULL != pEThread) ObDereferenceObject(pEThread);
    210.  
    211.     return NtStatus;
    212. }
    Протестировано на Windows 10 Threshold/20H2 и Windows 11 21H2.