cakemaker

contextjail.cpp

May 6th, 2025 (edited)
101
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 37.47 KB | Gaming | 0 0
  1. //
  2. // contextjail.cpp
  3. // Copyright (c) 2025 sixtyvividtails; provided under the MIT license.
  4. //
  5. // Contextjail test code. Compile using msvc.
  6. // 👉 thread: https://x.com/sixtyvividtails/status/1919833709080879298
  7. // 👉 compiled binary: https://pixeldrain.com/u/1pCYhms4
  8. //
  9. //
  10. //
  11. // TLDR: just spam NtGetContextThread(targetThread).
  12. // As a result, targetThread will jailed, repeatedly running nt!PspGetSetContextSpecialApc in ring0 at APC_LEVEL.
  13. //
  14. // Usecases:
  15. // 1. Pause thread midway in exploit races (even in ring0, since it's context retrieval APC is kernel APC).
  16. // 2. Or block entire CPU core. Since kernel APCs run at APC_LEVEL (🤯), thread scheduling would be kinda
  17. //    disabled (think priority = ∞). We use "witness" thread here to test that assumption, and it appears
  18. //    you'd actually need more than 1 prisoner latched to a particular core to really block that core.
  19. // 3. And more...
  20. //
  21. //
  22. //
  23. // Our setup (with default parameters):
  24. // 1. Master thread, pinned to cpu 0. It's the main thread, it just sets up and monitors stuff, prints stats.
  25. // 2. Prisoner thread, pinned to cpu 1. Has priority 15. Tries exec a few instructions, then sleeps for ~17 ms.
  26. //    Each successful execution of that couple of instructions treated as prison escape.
  27. //    Ideally thread won't execute user code at all until we let it.
  28. // 3. Witness thread, pinned to same cpu as the Prisoner thread. It has priority 31 (or 15, if not privileged).
  29. //    This thread is optional (/w 0 to disable). It's almost like the prisoner, but it sleeps for longer periods,
  30. //    counts execs as "witness moves" rather than escapes, and is not target of the NtGetContextThread spam.
  31. //    Main purpose of the witness thread is to check possibility to hijack cpu core.
  32. // 4. Jailer threads, by default we use 99 of them (mainly for win11 24H2), otherwise 0x10 can be enough.
  33. //    These threads just spam NtGetContextThread against the prisoner thread.
  34. //    We set their priority to 15 and affinity ~3 (so, any processors other than Master's cpu 0 and Prisoner's cpu 1).
  35. //    Their count is important, but their affinity mask is not so much - you can limit all of them to just 4 to 6
  36. //    processors, and that's often will be enough.
  37. //
  38. #include <stdio.h>
  39. #include <conio.h>
  40. #include <stdlib.h>
  41. #include <intrin.h>
  42. #include <stdint.h>
  43. #include "phnt.h"   // https://github.com/mrexodia/phnt-single-header
  44.  
  45.  
  46.  
  47. //----------------------------------------------------------------------------------------------------------------------
  48. // Defines.
  49. //----------------------------------------------------------------------------------------------------------------------
  50.  
  51. typedef unsigned char uchar, *puchar;
  52. typedef unsigned short ushort, *pushort;
  53. typedef unsigned int uint, *puint;
  54. typedef unsigned long ulong, *pulong;
  55. typedef long long int64, *pint64;
  56. typedef unsigned long long uint64, *puint64;
  57. typedef wchar_t wchar;
  58. typedef _Null_terminated_ char* pstr;
  59. typedef _Null_terminated_ wchar_t* pwstr;
  60. typedef _Null_terminated_ const char* pcstr;
  61. typedef _Null_terminated_ const wchar_t* pcwstr;
  62.  
  63. #define PAGE_SIZE                       0x1000
  64. #define PAGE_MASK                       0xFFF
  65.  
  66. #define ATTR_INLINE msvc::forceinline
  67.  
  68. static bool g_shouldRundown;
  69.  
  70.  
  71. //----------------------------------------------------------------------------------------------------------------------
  72. // Some helper routines.
  73. //----------------------------------------------------------------------------------------------------------------------
  74. // Copypasted/edited; refer to umt, don't copypaste from here.
  75.  
  76. /// <summary>
  77. /// NtSetInformationThread wrapper.
  78. /// </summary>
  79. /// <param name="infoclass"></param>
  80. /// <param name="data">We allow const; caller's responsibility is awareness of infoclasses which do modify data.</param>
  81. /// <param name="thread"></param>
  82. [[ATTR_INLINE]] DECLSPEC_SAFEBUFFERS
  83. inline NTSTATUS set_thread_info(THREADINFOCLASS infoclass, const auto& data, HANDLE thread = NtCurrentThread())
  84. {
  85.     return NtSetInformationThread(thread, infoclass, (PVOID)&data, sizeof(data));
  86. }
  87.  
  88.  
  89. /// <summary>
  90. /// Sets both ETHREAD.BasePriority and ETHREAD.Priority to the specified value.
  91. /// If new priority is not the same as the old ETHREAD.Priority, thread quantum is replenished.
  92. /// </summary>
  93. /// <param name="priority">Value in range [1;15] (or up to 31 if SeIncreaseBasePriority privilege enabled).</param>
  94. /// <param name="thread">Requires THREAD_SET_INFORMATION right.</param>
  95. [[ATTR_INLINE]] DECLSPEC_SAFEBUFFERS
  96. inline NTSTATUS set_thread_base_priority(uint priority, HANDLE thread = NtCurrentThread())
  97. {
  98.     return set_thread_info(ThreadActualBasePriority, priority, thread);
  99. }
  100.  
  101.  
  102. /// <summary>
  103. /// Sets thread boost state (reciprocal flag: ETHREAD.DisableBoost).
  104. /// </summary>
  105. /// <param name="enableBoost"></param>
  106. /// <param name="thread">Requires THREAD_SET_LIMITED_INFORMATION right.</param>
  107. [[ATTR_INLINE]] DECLSPEC_SAFEBUFFERS
  108. inline NTSTATUS set_thread_priority_boost(bool enableBoost, HANDLE thread = NtCurrentThread())
  109. {
  110.     BOOL disableBoost = !enableBoost;   // monkeys designed some apis
  111.     return set_thread_info(ThreadPriorityBoost, disableBoost, thread);
  112. }
  113.  
  114.  
  115. /// <summary>
  116. /// Sets thread affinity to a specified cpu group and mask.
  117. /// </summary>
  118. /// <param name="group"></param>
  119. /// <param name="thread">Requires THREAD_SET_INFORMATION right.</param>
  120. [[ATTR_INLINE]] DECLSPEC_SAFEBUFFERS
  121. inline NTSTATUS set_thread_affinity(const GROUP_AFFINITY& group, HANDLE thread = NtCurrentThread())
  122. {
  123.     return set_thread_info(ThreadGroupInformation, group, thread);
  124. }
  125.  
  126.  
  127. /// <summary>
  128. /// Sets thread name (for debugging).
  129. /// </summary>
  130. /// <param name="threadName"></param>
  131. /// <param name="thread"></param>
  132. [[ATTR_INLINE]] DECLSPEC_SAFEBUFFERS
  133. inline NTSTATUS set_thread_name(pcwstr threadName, HANDLE thread = NtCurrentThread())
  134. {
  135.     size_t len = wcslen(threadName);
  136.     UNICODE_STRING ustr{(ushort)(sizeof(wchar)*len), (ushort)(sizeof(wchar)*len), (pwstr)threadName};
  137.     return set_thread_info(ThreadNameInformation, ustr, thread);
  138. }
  139.  
  140.  
  141. inline bool is_wow64()
  142. {
  143. #ifdef _WIN64
  144.     return false;
  145. #else
  146.     return (uchar)USER_SHARED_DATA->ImageNumberLow == 0x64;
  147. #endif
  148. }
  149.  
  150. // lower 6 bits: cpu, higher bits: group
  151. static uint get_cpu_enumber()
  152. {
  153. #if defined (_M_ARM64)
  154.     uint id = (uint)_ReadStatusReg(ARM64_TPIDRRO_EL0);
  155.     return ((id & 0xFF00) | ((uchar)id << 2)) >> 2;
  156. #else
  157. #ifdef _WIN64
  158.     constexpr uint selector = 0x53;     // KGDT64_R3_CMTEB|RPL_MASK   vs   KGDT_R3_TEB|RPL_MASK
  159. #else
  160.     uint selector = is_wow64()? 0x53: 0x3B;
  161. #endif
  162.     uint segLimit = __segmentlimit(selector);
  163.     return ((segLimit >> 14) & 0x3F) | ((uchar)segLimit << 6);
  164. #endif
  165. }
  166.  
  167.  
  168. // retrieves valid cpu affinity mask for the current cpu group, in the laziest possible way (destroys current affinity)
  169. static GROUP_AFFINITY get_valid_cpu_affinities()
  170. {
  171.     ushort group = (ushort)(get_cpu_enumber() >> 6);
  172.     size_t mask = 0;
  173.     for (uint i = 0; i < 0x40; ++i)
  174.         if (SUCCEEDED(set_thread_affinity({.Mask{(size_t)(1ui64 << i)}, .Group{group}})))
  175.             mask |= (size_t)(1ui64 << i);
  176.     return {.Mask{mask}, .Group{group}};
  177. }
  178.  
  179.  
  180. static uchar popcount64(uint64 v)
  181. {
  182.     v = v - ((v >> 1) & 0x5555'5555'5555'5555);             // put count of each 2 bits into these 2 bits
  183.    v = (v & 0x3333'3333'3333'3333) + ((v >> 2) & 0x3333'3333'3333'3333);     // put count of each 4 bits
  184.    v = (v + (v >> 4)) & 0x0F0F'0F0F'0F0F'0F0F;             // put count of each 8 bits into these 8 bits
  185.     v = (v * 0x0101'0101'0101'0101) >> 0x38;                // left 8 bits of v + (v << 8) + (v << 0x10) + ...
  186.    return (uchar)v;
  187. }
  188.  
  189.  
  190. //----------------------------------------------------------------------------------------------------------------------
  191. // Actual test.
  192. //----------------------------------------------------------------------------------------------------------------------
  193.  
  194. struct TestData
  195. {
  196.    NTSTATUS status;
  197.    volatile bool prisonLocked;     // when enough jailers started jailing
  198.    volatile bool shouldRundown;
  199.  
  200.    uchar cpuGroup;
  201.    uchar jailersAffinityCount;
  202.    uint jailersCount;
  203.    uint witnessPriority;           // 0: no witness
  204.    int jailTimeSeconds;            // -1: until first escape
  205.    uint contextFlags;
  206.    size_t validAffinity;
  207.    size_t prisonerAffinity;        // also for witness
  208.    size_t jailersAffinity;
  209.  
  210.    HANDLE signalEvent;
  211.    HANDLE masterThread;
  212.    HANDLE prisonerThread;
  213.  
  214.    uint prisonerEscapes;
  215.    uint witnessMoves;
  216.  
  217.    uint tickPrisonLocked;
  218.    uint tickAllJailersJailing;
  219.    uint tickFirstPrisonerEscape;
  220.  
  221.    DECLSPEC_CACHEALIGN volatile uint jailersIndex;
  222.    DECLSPEC_CACHEALIGN volatile uint threadsReady;
  223.    DECLSPEC_CACHEALIGN volatile uint64 getThreadContextCalls;
  224. };
  225.  
  226.  
  227. // Returns exactly STATUS_SUCCESS to indicate thread should continue.
  228. // In that case caller must decrement data.threadsReady on return, otherwise it must not touch data.
  229. static NTSTATUS init_thread(pcwstr name, size_t affinity, uint priority, _Inout_ TestData* data)
  230. {
  231.    NTSTATUS st = set_thread_name(name);
  232.    st = set_thread_priority_boost(false);
  233.    st = set_thread_affinity({.Mask{affinity}, .Group{data->cpuGroup}});
  234.    if (SUCCEEDED(st))
  235.        st = set_thread_base_priority(priority);
  236.    if (FAILED(st))
  237.    {
  238.        data->status = st;
  239.        return st;
  240.    }
  241.    if (_InterlockedIncrement(&data->threadsReady) == 1 + data->jailersCount + !!data->witnessPriority)
  242.        NtAlertThread(data->masterThread);
  243.    if (data->shouldRundown)
  244.        return STATUS_ABANDONED;    // success status
  245.    st = NtWaitForSingleObject(data->signalEvent, false, {});
  246.    if (st == STATUS_WAIT_0 && data->shouldRundown)
  247.        st = STATUS_ABANDONED;    // success status
  248.    if (st != STATUS_WAIT_0)
  249.    {
  250.        static_assert(STATUS_WAIT_0 == STATUS_SUCCESS);
  251.        if (FAILED(st))
  252.            data->status = st;
  253.        _InterlockedDecrement(&data->threadsReady);
  254.        return st;
  255.    }
  256.    return STATUS_SUCCESS;
  257. }
  258.  
  259.  
  260. static NTSTATUS prisoner_or_witness_thread(_Inout_ TestData* data, bool isWitness = false)
  261. {
  262.    // note witness affinity by definition is same as prisoner affinity
  263.    NTSTATUS st = init_thread(isWitness? L"witness": L"prisoner", data->prisonerAffinity,
  264.        isWitness? data->witnessPriority: 15, data);
  265.    if (st != STATUS_SUCCESS)   // need to be exactly that to continue; otherwise may not touch data
  266.        return st;
  267.  
  268.    uint escapes = 0;
  269.    uint escapesLimit = isWitness || data->jailTimeSeconds >= 0? UINT_MAX: 0;
  270.    volatile uint* escapesPtr = isWitness? &data->witnessMoves: &data->prisonerEscapes;
  271.  
  272.    // allow to run while there's not enough jailers yet
  273.     volatile int spinner = 0;
  274.     while (!data->prisonLocked && !data->shouldRundown)
  275.         ++spinner;
  276.     if (spinner == INT_MAX && __rdtsc() == 0)   // never true, just to ensure spinner won't be optimized out
  277.         st = STATUS_INTERNAL_ERROR;
  278.     LARGE_INTEGER sleepAfterEscape{.QuadPart{isWitness? -250'000'0: -17'000'0}};
  279.  
  280.     while (!data->shouldRundown)
  281.     {
  282. #if defined(_M_IX86) || defined(_M_X64)
  283.         _mm_pause();
  284. #else
  285.         __yield();
  286. #endif
  287.         if (data->shouldRundown)
  288.             break;
  289.         *escapesPtr = ++escapes;
  290.         if (escapes == 1 && !isWitness)
  291.             data->tickFirstPrisonerEscape = NtGetTickCount();
  292.         if (escapes > escapesLimit)
  293.         {
  294.             st = STATUS_LOCK_NOT_GRANTED;
  295.             break;
  296.         }
  297.         NtDelayExecution(false, &sleepAfterEscape);
  298.     }
  299.  
  300.     NtAlertThread(data->masterThread);
  301.     _InterlockedDecrement(&data->threadsReady);
  302.     return st;
  303. }
  304.  
  305.  
  306. static NTSTATUS jailer_thread(_Inout_ TestData* data)
  307. {
  308.     // prepare local data
  309.     HANDLE prisoner = data->prisonerThread;
  310.     CONTEXT ctx;
  311.     ctx.ContextFlags = data->contextFlags;
  312.    
  313.     // prepare name and init this jailer thread
  314.     NTSTATUS st;
  315.     uint jailerId = _InterlockedIncrement(&data->jailersIndex) - 1;
  316.     {
  317.         wchar threadName[] = L"jailer AAAAAA";
  318.         for (pwstr p = threadName + 7; jailerId && *p; ++p)
  319.         {
  320.             *p += jailerId % 26;
  321.             jailerId /= 26;
  322.         }
  323.         st = init_thread(threadName, data->jailersAffinity, 15, data);
  324.     }
  325.     if (st != STATUS_SUCCESS)   // need to be exactly that to continue; otherwise may not touch data
  326.         return st;
  327.  
  328.     // try initial context retrieval
  329.     st = NtGetContextThread(prisoner, &ctx);
  330.     _InterlockedIncrement(&data->getThreadContextCalls);    // stats
  331.     if (SUCCEEDED(st))  [[likely]]
  332.     {
  333.         // assume prison is locked once at least 6 jailers started jailing (or less if there's less than 6 jailers)
  334.         uint jailersCount = data->jailersCount;
  335.         uint prisonLockWhen = jailersCount > 6? 6: jailersCount;
  336.         uint jailersJailing = _InterlockedIncrement(&data->jailersIndex) - jailersCount;
  337.         uint tickNow = NtGetTickCount();
  338.         if (jailersJailing == prisonLockWhen)
  339.         {
  340.             data->prisonLocked = true;      // enough jailers at work, prisoner shouldn't run anymore
  341.             data->tickPrisonLocked = tickNow;
  342.         }
  343.         if (jailersJailing == jailersCount)
  344.             data->tickAllJailersJailing = tickNow;
  345.     }
  346.     // now just spam NtGetContextThread
  347.     while (!data->shouldRundown && SUCCEEDED(st))
  348.     {
  349.         st = NtGetContextThread(prisoner, &ctx);
  350.         _InterlockedIncrement(&data->getThreadContextCalls);
  351.     }
  352.     if (FAILED(st) && (st != STATUS_UNSUCCESSFUL || !data->shouldRundown))
  353.     {
  354.         // we get STATUS_UNSUCCESSFUL when prisoner thread is running down (apc queueuing gets disabled)
  355.         data->status = st;
  356.         NtAlertThread(data->masterThread);
  357.     }
  358.  
  359.     _InterlockedDecrement(&data->threadsReady);
  360.     return st;
  361. }
  362.  
  363.  
  364. static NTSTATUS test_context_jail2(_Inout_ TestData* data)
  365. {
  366.     printf("[ ] initializing threads...\n");
  367.    
  368.     uint threadFlags = THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH;
  369.     if (USER_SHARED_DATA->NtMajorVersion >= 10)
  370.         threadFlags |= THREAD_CREATE_FLAGS_SKIP_LOADER_INIT;
  371.    
  372.     for (uint i = 0; i < 2; ++i)
  373.     {
  374.         HANDLE thread{};
  375.         NTSTATUS st = NtCreateThreadEx(&thread, THREAD_GET_CONTEXT, {}, NtCurrentProcess(), [](PVOID arg)
  376.         {
  377.             return prisoner_or_witness_thread((TestData*)((size_t)arg & ~1), !!((size_t)arg & 1));
  378.         }, (PVOID)((size_t)data | i), threadFlags, 0, 0x4000, 0x8000, {});
  379.         if (FAILED(st))
  380.             return st;
  381.         if (i == 0)
  382.             data->prisonerThread = thread;
  383.         else
  384.             NtClose(thread);
  385.         if (!data->witnessPriority)
  386.             break;
  387.     }
  388.     uint threadsReadyTarget = 1 + !!data->witnessPriority + data->jailersCount;
  389.     for (uint i = 0; i < data->jailersCount; ++i)
  390.     {
  391.         if (FAILED(data->status))
  392.         {
  393.             printf("[x] some child thread failed: %08X, threads ready: %u/%u\n",
  394.                 data->status, data->threadsReady, threadsReadyTarget);
  395.             return STATUS_THREAD_NOT_RUNNING;
  396.         }
  397.         if (g_shouldRundown)
  398.         {
  399.             printf("[!] cancelled\n");
  400.             return STATUS_CANCELLED;
  401.         }
  402.         HANDLE thread{};
  403.         NTSTATUS st = NtCreateThreadEx(&thread, SYNCHRONIZE, {}, NtCurrentProcess(), [](PVOID arg)
  404.         {
  405.             return jailer_thread((TestData*)arg);
  406.         }, data, threadFlags, 0, 0x4000, 0x8000, {});
  407.         if (FAILED(st))
  408.             return st;
  409.         NtClose(thread);
  410.     }
  411.  
  412.     printf("[ ] waiting for threads to finish init...\n");
  413.     NTSTATUS st = STATUS_SUCCESS;
  414.     for (uint retries = 0; data->threadsReady < threadsReadyTarget && retries < 60; ++retries)
  415.     {
  416.         if (retries == 5)
  417.             printf("[!] still waiting, threads done: %u/%u\n", data->threadsReady, threadsReadyTarget);
  418.         if (FAILED(data->status))
  419.         {
  420.             printf("[x] some child thread failed: %08X, threads ready: %u/%u\n",
  421.                 data->status, data->threadsReady, threadsReadyTarget);
  422.             return STATUS_THREAD_NOT_RUNNING;
  423.         }
  424.         if (g_shouldRundown)
  425.         {
  426.             printf("[!] cancelled\n");
  427.             return STATUS_CANCELLED;
  428.         }
  429.         LARGE_INTEGER timeout{.QuadPart{-1'000'000'0}};
  430.        st = NtDelayExecution(true, &timeout);
  431.    }
  432.    if (data->threadsReady < threadsReadyTarget)
  433.    {
  434.        printf("[x] failed to init threads, threads ready: %u/%u", data->threadsReady, threadsReadyTarget);
  435.        return STATUS_THREAD_NOT_IN_PROCESS;
  436.    }
  437.  
  438.    if (st != STATUS_ALERTED)
  439.    {
  440.        // to minimize chance of spurious wakeup
  441.        printf("[ ] waiting for the final alert...\n");
  442.        LARGE_INTEGER timeout{.QuadPart{-1'000'000'0}};
  443.         st = NtDelayExecution(true, &timeout);      // and ignore if alert never comes
  444.     }
  445.     printf("[+] all threads ready\n");
  446.     if (g_shouldRundown)
  447.     {
  448.         printf("[!] cancelled\n");
  449.         return STATUS_CANCELLED;
  450.     }
  451.    
  452.     int jailTime = data->jailTimeSeconds;
  453.     if (jailTime < 0)
  454.         printf("[ ] signaling threads and waiting until jail escape...\n");
  455.     else
  456.         printf("[ ] signaling threads and waiting for %u seconds...\n", data->jailTimeSeconds);
  457.  
  458.     uint64 ticks0 = NtGetTickCount64();
  459.     uint64 ticksTarget = jailTime >= 0? ticks0 + 1000ui64 * jailTime: UINT64_MAX;
  460.     LARGE_INTEGER timeout{.QuadPart{-1'000'000'0i64 * jailTime}};
  461.    if (jailTime < 0 || jailTime > 10)
  462.        timeout.QuadPart = -4'500'000'0;
  463.     st = NtSignalAndWaitForSingleObject(data->signalEvent, NtCurrentThread(), true, &timeout);
  464.     while (!g_shouldRundown && SUCCEEDED(data->status) && (data->jailTimeSeconds >= 0 || data->prisonerEscapes == 0))
  465.     {
  466.         uint64 ticksNow = NtGetTickCount64();
  467.         if (ticksNow > ticksTarget - 250)
  468.             break;
  469.         double elapsed = (ticksNow - ticks0) / 1000.0;
  470.         printf("[*] elapsed %.01f/%d seconds, prisoner escapes: %u, witness moves: %u, getContext calls: %I64u\n",
  471.             elapsed, jailTime, data->prisonerEscapes, data->witnessMoves, data->getThreadContextCalls);
  472.         uint64 delta = ticksTarget - ticksNow;
  473.         timeout.QuadPart = delta < 12'000? -1'000'0i64 * delta: -9'500'000'0;
  474.         NtDelayExecution(true, &timeout);
  475.     }
  476.  
  477.     double elapsed = (NtGetTickCount64() - ticks0) / 1000.0;
  478.     int allJailersJailingDelta = data->tickAllJailersJailing?
  479.         data->tickAllJailersJailing - data->tickPrisonLocked: 0;    // might still get negative coz preemption
  480.     int firstEscapeDelta = (data->tickPrisonLocked && data->tickFirstPrisonerEscape)?
  481.         data->tickFirstPrisonerEscape - data->tickPrisonLocked: 0;  // again, might get negative here
  482.     printf("[+] done; elapsed %.01f/%d seconds, threads status: %08X, rundown enforced: %u\n"
  483.         "    prisoner escapes: %u, witness moves: %u, getContext calls: %I64u\n"
  484.         "    ticks delta: prison locked: 0, all jailers working: %d ms, first escape: %d ms\n\n",
  485.         elapsed, jailTime, data->status, g_shouldRundown,
  486.         data->prisonerEscapes, data->witnessMoves, data->getThreadContextCalls,
  487.         allJailersJailingDelta, firstEscapeDelta);
  488.     if (data->prisonerEscapes)
  489.     {
  490.         printf("[x] >>>>>>>> PRISONER ESCAPED, remediation needed\n");
  491.         if (data->jailersAffinityCount < 6 || data->jailersCount < 99)
  492.             printf("    possible reason: jailers processors below 6, or jailers count below 99\n\n");
  493.         else
  494.             printf("    possible reason: unknown; but try using more jailers or more jailer processors\n\n");
  495.         return STATUS_SYSTEM_NEEDS_REMEDIATION;
  496.     }
  497.     st = data->status;
  498.     if (FAILED(st))
  499.     {
  500.         printf("[*] >>>>>>>> PRISONER status unknown: %08X, remediation needed\n\n", st);
  501.         return st;
  502.     }
  503.     printf("[+] >>>>>>>> PRISONER STILL JAILED, all good\n\n");
  504.     if (g_shouldRundown && jailTime >= 0)
  505.         return STATUS_CANCELLED;
  506.     return STATUS_SUCCESS;
  507. }
  508.  
  509.  
  510. static NTSTATUS test_context_jail(uint jailersCount, int jailTimeSeconds, uint witnessPriority,
  511.     uint64 jailersAffinity, uint64 prisonerAffinity, bool fullContext)
  512. {
  513.     if (is_wow64())
  514.     {
  515.         printf("[x] wow64 is not supported\n"); // technically it's fine, but requires some tinkering with native api
  516.         return STATUS_WOW_ASSERTION;
  517.     }
  518.     GROUP_AFFINITY validAffinity = get_valid_cpu_affinities();
  519.     printf("[ ] affinity mask for cpu group %u: %016I64X\n", validAffinity.Group, (uint64)validAffinity.Mask);
  520.    
  521.     // we're the master thread, set name and affinity
  522.     set_thread_name(L"master");
  523.     NTSTATUS st = set_thread_affinity({.Mask{1}, .Group{validAffinity.Group}});
  524.     if (FAILED(st) || (validAffinity.Mask & (validAffinity.Mask - 1)) == 0)
  525.     {
  526.         // we need at least 2 bits set
  527.         printf("[x] valid affinity mask unsuitable for test\n");
  528.         return STATUS_CPU_SET_INVALID;
  529.     }
  530.  
  531.     TestData data
  532.     {
  533.         .cpuGroup = (uchar)validAffinity.Group,
  534.         .jailersAffinityCount = popcount64(jailersAffinity & validAffinity.Mask),
  535.         .jailersCount = jailersCount,
  536.         .witnessPriority = witnessPriority,
  537.         .jailTimeSeconds = jailTimeSeconds,
  538.         .contextFlags = (uint)(fullContext? CONTEXT_ALL: CONTEXT_CONTROL),  // LATER: CONTEXT_XSTATE?
  539.         .validAffinity = validAffinity.Mask,
  540.         .prisonerAffinity = prisonerAffinity & validAffinity.Mask,
  541.         .jailersAffinity = jailersAffinity & validAffinity.Mask,
  542.     };
  543.  
  544.     if ((data.prisonerAffinity & (data.prisonerAffinity - 1)) != 0)
  545.         data.witnessPriority = 0;       // don't use witness when prisoner affinity spans multiple cpus
  546.     if (!data.prisonerAffinity || !data.jailersAffinity)
  547.     {
  548.         printf("[x] updated prisoner affinity %016I64X or jailers affinity %016I64X unsuitable for test\n",
  549.             (uint64)data.prisonerAffinity, (uint64)data.jailersAffinity);
  550.         return STATUS_CPU_SET_INVALID;
  551.     }
  552.     BOOLEAN wasEnabled{true};
  553.     if (data.witnessPriority > 15)
  554.     {
  555.         if (FAILED(RtlAdjustPrivilege(SE_INC_BASE_PRIORITY_PRIVILEGE, true, false, &wasEnabled)))
  556.         {
  557.             printf("[!] unable to adjust SeIncreaseBasePriority privilege, witness will be limited to priority 15\n");
  558.             data.witnessPriority = 15;
  559.         }
  560.     }
  561.    
  562.     st = NtCreateEvent(&data.signalEvent, EVENT_ALL_ACCESS, {}, NotificationEvent, false);
  563.     if (FAILED(st))
  564.         return st;
  565.     st = NtDuplicateObject(NtCurrentProcess(), NtCurrentThread(), NtCurrentProcess(), &data.masterThread,
  566.         THREAD_ALL_ACCESS, 0, 0);
  567.     if (FAILED(st))
  568.     {
  569.         NtClose(data.signalEvent);
  570.         return st;
  571.     }
  572.  
  573.     if (data.jailersAffinityCount < 4)
  574.         printf("[!] WARNING: jailers processors count is %u, which is quite low, consider using more CPUs\n",
  575.             data.jailersAffinityCount);
  576.     if (data.jailersCount < 12)
  577.         printf("[!] WARNING: jailers count is %u, which is quite low, consider using more jailers\n",
  578.             data.jailersCount);
  579.  
  580.     char witnessInfo[0x20];
  581.     witnessInfo[0] = {};
  582.     if (data.witnessPriority)
  583.         sprintf_s(witnessInfo, ", priority %u", data.witnessPriority);
  584.     char jailTimeString[0x20];
  585.     strcpy_s(jailTimeString, "until jail escape");
  586.     if (data.jailTimeSeconds >= 0)
  587.         sprintf_s(jailTimeString, "%u seconds", data.jailTimeSeconds);
  588.     printf("[+] ready to test, parameters:\n"
  589.         "    prisoner affinity: %016I64X, witness: %s%s\n"
  590.         "    jailers  affinity: %016I64X, %u processors; jailers count: %u\n"
  591.         "    timelimit: %s\n",
  592.         (uint64)data.prisonerAffinity, data.witnessPriority? "enabled": "disabled", witnessInfo,
  593.         (uint64)data.jailersAffinity, data.jailersAffinityCount, data.jailersCount,
  594.         jailTimeString);
  595.     st = test_context_jail2(&data);
  596.     data.shouldRundown = true;
  597.     NtSetEvent(data.signalEvent, {});
  598.     printf("[ ] waiting for threads to rundown...\n");
  599.     for (uint retries = 0; data.threadsReady && retries < 300; ++retries)
  600.     {
  601.         if (retries == 30)
  602.             printf("[!] still waiting, threads left: %u\n", data.threadsReady);
  603.         LARGE_INTEGER timeout{.QuadPart{-100'000'0}};
  604.         NtDelayExecution(true, &timeout);
  605.     }
  606.     if (data.threadsReady)
  607.         printf("[x] gave up waiting, threads left: %u; that's potentially fatal\n", data.threadsReady);
  608.     NtClose(data.signalEvent);
  609.     NtClose(data.masterThread);
  610.     if (data.prisonerThread)
  611.         NtClose(data.prisonerThread);
  612.     return st;
  613. }
  614.  
  615.  
  616. //----------------------------------------------------------------------------------------------------------------------
  617. // Entry/diag/args/etc.
  618. //----------------------------------------------------------------------------------------------------------------------
  619. // Parts just copypasted in from umt; don't use it from here, refer to the lib instead.
  620.  
  621. static void print_help()
  622. {
  623.     printf(R"(
  624. contextjail.exe - ContextJail test.
  625.  
  626. Arguments:
  627.  /j     JAILERS_COUNT          - specifies number of jailers (default 99)
  628.  /t     JAIL_TIME              - time to spend in jail, seconds (default 30); -1: till escape
  629.  /f                            - retrieve CONTEXT_ALL (otherwise just CONTEXT_CONTROL)
  630.  /b                            - batch mode (never pause at the end)
  631.  /w     WITNESS_PRIORITY       - priority of the witness thread (default 31); -1: no witness
  632.  /jaff  JAILERS_AFFINITY       - uint64 affinity mask for jailers (default ~3)
  633.  /paff  PRISONER_AFFINITY      - uint64 affinity mask for the prisoner (default 2)
  634.  
  635. Note for simplicity we mostly use currently assigned cpu group for all threads, and cpu 0 for the master thread.
  636. We won't add witness if prisoner's affinity spans more than 1 cpu.
  637. )");
  638. }
  639.  
  640.  
  641. struct Args
  642. {
  643.     uint jailersCount{};
  644.     int jailTimeSeconds{};
  645.     int witnessPriority{};
  646.     uint64 jailersAffinity{};
  647.     uint64 prisonerAffinity{};
  648.     bool fullContext: 1{};
  649.     bool batch: 1{};
  650.     bool help: 1{};
  651.  
  652.     bool parse(int argc, _In_ pcwstr argv[])
  653.     {
  654.         for (int i = 0; i < argc; ++i)
  655.         {
  656.             auto arg = argv[i];
  657.             if (arg[0] != '/' && arg[0] != '-')
  658.                 continue;
  659.             ++arg;
  660.             if (_wcsicmp(arg, L"?") == 0)
  661.                 help = true;
  662.             if (_wcsicmp(arg, L"b") == 0)
  663.                 batch = true;
  664.         }
  665.         for (int i = 0; i < argc; ++i)
  666.         {
  667.             auto arg = argv[i];
  668.             if (arg[0] != '/' && arg[0] != '-')     // might(!) be app name
  669.             {
  670.                 if (i == 0)
  671.                     continue;
  672.                 printf("[x] unexpected arg %d: %S\n", i, arg);
  673.                 return false;
  674.             }
  675.             ++arg;
  676.             if (_wcsicmp(arg, L"?") == 0 || _wcsicmp(arg, L"b") == 0)
  677.                 continue;
  678.             if (_wcsicmp(arg, L"f") == 0)
  679.             {
  680.                 fullContext = true;
  681.                 continue;
  682.             }
  683.             int what = 1 * !!!_wcsicmp(arg, L"j") + 2 * !!!_wcsicmp(arg, L"t") + 4 * !!!_wcsicmp(arg, L"w")
  684.                 + 8 * !!!_wcsicmp(arg, L"jaff") + 0x10 * !!!_wcsicmp(arg, L"paff");     // "!!!" is for linter, lol
  685.             if (!what)
  686.             {
  687.                 printf("[x] unexpected arg %d: %S\n", i, arg);
  688.                 return false;
  689.             }
  690.  
  691.             if (++i >= argc)
  692.             {
  693.                 printf("[x] value absent for arg %d %S\n", i-1, arg-1);
  694.                 return false;
  695.             }
  696.             bool notop{};
  697.             pcwstr p = argv[i];
  698.             while (*p == ' ')
  699.                 ++p;
  700.             notop = (*p == '~');
  701.             if (notop)
  702.                 ++p;
  703.             wchar clear[0x80];
  704.             pwstr d = clear;
  705.             for (; *p && d < clear + _countof(clear) - 1; ++p)
  706.                 if (*p != '\'' && *p != '`' && *p != '_')
  707.                     *d++ = *p;
  708.             while (*p == ' ')
  709.                 ++p;
  710.             *d = {};
  711.             wchar* end{};
  712.             int64 v = _wcstoi64(clear, &end, 0);
  713.             if (notop)
  714.                 v = ~v;
  715.             if (*p
  716.                 || (!v && (!end || end == clear))
  717.                 || ((v < -1 || v > INT_MAX) && what <= 4)
  718.                 || (v < 0 && what == 1))
  719.             {
  720.                 printf("[x] invalid value for arg %d %S: %S\n", i-1, arg-1, argv[i]);
  721.                 return false;
  722.             }
  723.             if (what == 1)
  724.                 jailersCount = (uint)v;
  725.             else if (what == 2)
  726.                 jailTimeSeconds = (int)v;
  727.             else if (what == 4)
  728.                 witnessPriority = (int)v;
  729.             else if (what == 8)
  730.                 jailersAffinity = v;
  731.             else
  732.                 prisonerAffinity = v;
  733.         }
  734.         normalize();
  735.         return true;
  736.     }
  737.  
  738.  
  739.     void normalize()
  740.     {
  741.         if (!jailersCount)
  742.             jailersCount = 99;      // JAILERS_COUNT default
  743.         if (!jailTimeSeconds)
  744.             jailTimeSeconds = 30;   // JAIL_TIME default
  745.         if (witnessPriority < 0)
  746.             witnessPriority = 0;    // no witness
  747.         else if (witnessPriority == 0 || witnessPriority > 31)
  748.             witnessPriority = 31;   // WITNESS_PRIORITY default, would become 15 if no SeIncreaseBasePriority privilege
  749.         if (!prisonerAffinity)
  750.             prisonerAffinity = 2;   // PRISONER_AFFINITY default
  751.         if (!jailersAffinity)
  752.         {
  753.             jailersAffinity = ~3ui64;       // JAILERS_AFFINITY default
  754.             if ((prisonerAffinity & (prisonerAffinity - 1)) == 0)   // JAILERS_AFFINITY when prisoner signle bit set
  755.                 jailersAffinity = ~1ui64 & ~prisonerAffinity;       // all but bit0 [master thread], and prisoner
  756.             if (!jailersAffinity)
  757.                 jailersAffinity = ~3ui64;   // whatever, go default
  758.         }
  759.     }
  760. };
  761.  
  762.  
  763. DECLSPEC_SAFEBUFFERS
  764. static inline NTSTATUS get_cpu_vendor(
  765.     _Always_(_Post_z_) _Out_z_cap_post_count_(vendorBufSize, 13) char* vendor,
  766.     size_t vendorBufSize)
  767. {
  768.     if (vendorBufSize < 13) [[unlikely]]
  769.     {
  770.         if (vendorBufSize)
  771.             *vendor = {};
  772.         return STATUS_BUFFER_TOO_SMALL;
  773.     }
  774.     int regs[4];
  775.     __cpuidex(regs, 0, 0);
  776.     (puint(vendor))[0] = regs[1];
  777.     (puint(vendor))[1] = regs[3];
  778.     (puint(vendor))[2] = regs[2];
  779.     vendor[12] = {};
  780.     return STATUS_SUCCESS;
  781. }
  782.  
  783.  
  784. DECLSPEC_SAFEBUFFERS
  785. static inline NTSTATUS get_cpu_brand(
  786.     _Always_(_Post_z_) _Out_z_cap_post_count_(brandBufSize, 0x30) char* brand,
  787.     size_t brandBufSize)
  788. {
  789.     if (brandBufSize < 0x30) [[unlikely]]
  790.     {
  791.         if (brandBufSize)
  792.             *brand = {};
  793.         return STATUS_BUFFER_TOO_SMALL;
  794.     }
  795.     for (uint i = 0; i < 3; ++i)
  796.     {
  797.         int regs[4];
  798.         __cpuidex(regs, 0x8000'0002 + i, 0);
  799.        (puint(brand))[i*4 + 0] = regs[0];
  800.        (puint(brand))[i*4 + 1] = regs[1];
  801.        (puint(brand))[i*4 + 2] = regs[2];
  802.        (puint(brand))[i*4 + 3] = regs[3];
  803.    }
  804.    brand[0x2F] = {};     // should be null-terminated, but we enforce that
  805.    return STATUS_SUCCESS;
  806. }
  807.  
  808.  
  809. DECLSPEC_SAFEBUFFERS
  810. static inline NTSTATUS get_cpu_brand_trimmed(
  811.    _Always_(_Post_z_) _Out_z_cap_(brandBufSize) char* brand,
  812.    size_t brandBufSize)
  813. {
  814.    if (brandBufSize)
  815.        *brand = {};
  816.    char buf[0x30];
  817.    NTSTATUS st = get_cpu_brand(buf, _countof(buf));
  818.    if (FAILED(st))
  819.        return st;
  820.    char* p = buf;
  821.    while (*p == ' ')
  822.        ++p;
  823.    char* e = &buf[0x2E];
  824.    while (e >= p && (!*e || *e == ' '))
  825.        --e;
  826.    ++e;
  827.    size_t toCopy = e - p + 1;
  828.    if (brandBufSize < toCopy) [[unlikely]]
  829.        return STATUS_BUFFER_TOO_SMALL;
  830.    *e = {};
  831.    for (uint i = 0; i < toCopy; ++i)
  832.        *brand++ = *p++;
  833.    return STATUS_SUCCESS;
  834. }
  835.  
  836.  
  837. DECLSPEC_SAFEBUFFERS
  838. _Success_(return >= 0)
  839. static inline int sprintf_sx(
  840.    _Out_writes_z_(bufferCount) _Always_(_Post_z_) char* buffer,
  841.    _In_ size_t bufferCount,
  842.    _In_z_ _Printf_format_string_ pcstr format,
  843.    ...
  844. )
  845. {
  846.    va_list args;
  847.    va_start(args, format);
  848.    return vsprintf_s(buffer, bufferCount, format, args);
  849. }
  850.  
  851.  
  852. static NTSTATUS FASTCALL format_info_banner(
  853.    _In_opt_ const char* componentName,
  854.    _Out_writes_z_(bufferSize) _Always_(_Post_z_) char* buffer,
  855.    size_t bufferSize)
  856. {
  857.    char cpuVendor[0x10];
  858.    get_cpu_vendor(cpuVendor, _countof(cpuVendor));
  859.    char cpuBrand[0x30];
  860.    get_cpu_brand_trimmed(cpuBrand, _countof(cpuBrand));
  861.  
  862.    // basic hypervisor info; refer to hv/mc for proper
  863.    char hvBrand[0x14]{};
  864.    int regs[4]{};
  865.    __cpuidex(regs, 0x4000'0000, 0);    // retrieves max leaf for this range, and string like 'Microsoft Hv'
  866.     uint hyperMaxLeaf = regs[0];
  867.     if (hyperMaxLeaf < 0x8000'0000u && (hyperMaxLeaf > 0x4000'0000 || (hyperMaxLeaf == 0x4000'0000 && regs[2])))
  868.    {
  869.        // condition above should cover us in case there's no proper hypervisor (and cpu ignored this leaf properly)
  870.         (puint(hvBrand))[0] = regs[1];      // ebx
  871.         (puint(hvBrand))[1] = regs[2];      // ecx
  872.         (puint(hvBrand))[2] = regs[3];      // edx
  873.         if (hyperMaxLeaf >= 0x4000'0001)
  874.        {
  875.            __cpuidex(regs, 0x4000'0001, 0);
  876.             size_t len = strlen(hvBrand);
  877.             if (len && regs[0])             // e.g. "Hv#1"
  878.                 hvBrand[len++] = '/';
  879.             *puint(&hvBrand[len]) = regs[0];
  880.         }
  881.         for (char& c: hvBrand)
  882.             if (c && (c < ' ' || c > 0x7F))
  883.                 c = '.';
  884.     }
  885.     if (!hvBrand[0])
  886.         *puint(hvBrand) = '-';
  887.     __cpuidex(regs, 1, 0);
  888.     uint ecx = regs[2];
  889.     bool hypervisorPresent = (ecx >> 0x1F) != 0;
  890.  
  891.     SYSTEM_CODEINTEGRITY_INFORMATION ciInfo{.Length{sizeof(ciInfo)}};
  892.     if (FAILED(NtQuerySystemInformation(SystemCodeIntegrityInformation, &ciInfo, sizeof(ciInfo), {})))
  893.         ciInfo.CodeIntegrityOptions = 0;
  894.  
  895.     uint timestamp = ((IMAGE_NT_HEADERS*)((SIZE_T)&__ImageBase + __ImageBase.e_lfanew))->FileHeader.TimeDateStamp;
  896.     int rez = sprintf_sx(buffer, bufferSize,
  897.         "%s started, brid %08X x%02u\n"
  898.         "    os %u.%u.%05u, img %04X, server: %u; bootId: %u\n"
  899.         "    cpus: %u/%u, groups: %u, ram: %u MB; cpu: %s, %s\n"
  900.         "    hypervisor: %s, present: %u, CodeIntegrity: %08X, SecureBoot: %u, KernelDebugger: %02X",
  901.         componentName? componentName: "exe", timestamp, (uint)(sizeof(void*) * CHAR_BIT),
  902.         USER_SHARED_DATA->NtMajorVersion, USER_SHARED_DATA->NtMinorVersion, USER_SHARED_DATA->NtBuildNumber,
  903.         USER_SHARED_DATA->ImageNumberLow, USER_SHARED_DATA->NtProductType > NtProductWinNt, USER_SHARED_DATA->BootId,
  904.         USER_SHARED_DATA->UnparkedProcessorCount, USER_SHARED_DATA->ActiveProcessorCount,
  905.         USER_SHARED_DATA->ActiveGroupCount, USER_SHARED_DATA->NumberOfPhysicalPages/(1024*1024/0x1000),
  906.         cpuVendor, cpuBrand,
  907.         hvBrand, hypervisorPresent,
  908.         ciInfo.CodeIntegrityOptions, USER_SHARED_DATA->DbgSecureBootEnabled, USER_SHARED_DATA->KdDebuggerEnabled);
  909.     if (rez <= 0)
  910.     {
  911.         *buffer = {};
  912.         return STATUS_BUFFER_TOO_SMALL;
  913.     }
  914.     return STATUS_SUCCESS;
  915. }
  916.  
  917.  
  918. static void FASTCALL print_info_banner(_In_opt_ pcstr componentName)
  919. {
  920.     char banner[0x800];
  921.     if (FAILED(format_info_banner(componentName, banner, _countof(banner))))
  922.         strcpy_s(banner, "<info_error>");
  923.     printf("%s\n", banner);
  924. }
  925.  
  926.  
  927. static void FASTCALL wait_console_keypress_if_needed(_In_opt_ const char* message)
  928. {
  929.     ULONG dummy;
  930.     bool needPause =
  931.         GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &dummy)     // success, this means output is not redirected
  932.         && GetConsoleProcessList(&dummy, 1) <= 1;                   // single console process: it's our process, no cmd
  933.     if (!needPause)
  934.         return;
  935.  
  936.     if (message)
  937.         printf("%s\n", message);
  938.     _flushall();
  939.     int c = _getch();
  940.     if (!c || c == 0xE0)    // arrow or function key, read one more char to drain queue
  941.         (void)_getch();
  942. }
  943.  
  944.  
  945. static void init()
  946. {
  947.     SetErrorMode(SEM_NOOPENFILEERRORBOX);
  948.     _set_invalid_parameter_handler([](pcwstr expr, pcwstr func, pcwstr, uint line, uintptr_t)
  949.     {
  950.         printf("WARNING: invalid parameter handler invoked; func: '%S', line: %u, expr: '%S'\n", func, line, expr);
  951.     });
  952.  
  953.     static HANDLE mainThread;   // for Ctrl+C, won't be released
  954.     NtDuplicateObject(NtCurrentProcess(), NtCurrentThread(), NtCurrentProcess(), &mainThread, THREAD_ALERT, 0, 0);
  955.  
  956.     SetConsoleCtrlHandler([](ulong ctrl) -> BOOL
  957.     {
  958.         if (ctrl != CTRL_C_EVENT && ctrl != CTRL_BREAK_EVENT && ctrl != CTRL_CLOSE_EVENT
  959.             && ctrl != CTRL_LOGOFF_EVENT && ctrl != CTRL_SHUTDOWN_EVENT)
  960.         {
  961.             return false;
  962.         }
  963.         printf("[!] got ctrl+xxx signal: %u...\n", ctrl);
  964.         g_shouldRundown = true;
  965.         NtAlertThread(mainThread);
  966.         return true;    // handled
  967.     }, TRUE);
  968. }
  969.  
  970.  
  971. int __cdecl wmain(int argc, const wchar* argv[])
  972. {
  973.     init();
  974.     print_info_banner("ContextJail test");
  975.    
  976.     Args args{};
  977.     bool parseOk = args.parse(argc, argv);
  978.     if (!parseOk || args.help)
  979.     {
  980.         print_help();
  981.         if (!args.batch)
  982.             wait_console_keypress_if_needed("Press any key to exit...");
  983.         return parseOk? 0: -1;
  984.     }
  985.  
  986.     uint64 ticks0 = NtGetTickCount64();
  987.     NTSTATUS st = test_context_jail(args.jailersCount, args.jailTimeSeconds, args.witnessPriority,
  988.         args.jailersAffinity, args.prisonerAffinity, args.fullContext);
  989.     printf("ContextJail status: %08X; spent %u seconds\n", st, (uint)((NtGetTickCount64() - ticks0)/1000u));
  990.  
  991.     if (!args.batch)
  992.         wait_console_keypress_if_needed("Press any key to exit...");
  993.     return (int)st;
  994. }
  995.  
Advertisement
Add Comment
Please, Sign In to add comment