Merge pull request #1 from Cobalt-Strike/CallStackMaskerv1

First version of CallStackMasker
This commit is contained in:
william-burgess
2023-02-13 16:51:12 +00:00
committed by GitHub
5 changed files with 1505 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33205.214
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CallStackMasker", "CallStackMasker\CallStackMasker.vcxproj", "{041BC7B3-B050-48FA-B587-68172C9E0597}"
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
{041BC7B3-B050-48FA-B587-68172C9E0597}.Debug|x64.ActiveCfg = Debug|x64
{041BC7B3-B050-48FA-B587-68172C9E0597}.Debug|x64.Build.0 = Debug|x64
{041BC7B3-B050-48FA-B587-68172C9E0597}.Debug|x86.ActiveCfg = Release|Win32
{041BC7B3-B050-48FA-B587-68172C9E0597}.Release|x64.ActiveCfg = Release|x64
{041BC7B3-B050-48FA-B587-68172C9E0597}.Release|x64.Build.0 = Release|x64
{041BC7B3-B050-48FA-B587-68172C9E0597}.Release|x86.ActiveCfg = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9202ACBE-EAF2-4F09-B535-AAE91C61A7DA}
EndGlobalSection
EndGlobal
+189
View File
@@ -0,0 +1,189 @@
#include "CallStackUtils.h"
// Static spoofed call stack taken from spoolsv.exe via SysInternals' Process Explorer.
// ntdll.dll!NtWaitForSingleObject + 0x14
// KERNELBASE.dll!WaitForSingleObjectEx + 0x8e
// localspl.dll!InitializePrintMonitor2 + 0xb7a
// KERNEL32.DLL!BaseThreadInitThunk + 0x14
// ntdll.dll!lRtlUserThreadStart + 0x21
// Start address: localspl.dll!InitializePrintMonitor2 + 0xb20.
std::vector<StackFrame> spoofedCallStack =
{
StackFrame(L"C:\\Windows\\SYSTEM32\\kernelbase.dll", "WaitForSingleObjectEx", 0x8e, 0, FALSE),
StackFrame(L"C:\\Windows\\SYSTEM32\\localspl.dll", "InitializePrintMonitor2", 0xb7a, 0 , TRUE),
StackFrame(L"C:\\Windows\\SYSTEM32\\kernel32.dll", "BaseThreadInitThunk", 0x14, 0, FALSE),
StackFrame(L"C:\\Windows\\SYSTEM32\\ntdll.dll", "RtlUserThreadStart", 0x21, 0, FALSE),
};
// Global struct to store target thread info.
threadToSpoof targetThreadToSpoof = {};
void MaskCallStack(DWORD SleepTime)
{
CONTEXT ctxThread = { 0 };
CONTEXT ropBackUpStack = { 0 };
CONTEXT ropSpoofStack = { 0 };
CONTEXT ropRestoreStack = { 0 };
CONTEXT ropSetEvent = { 0 };
HANDLE hTimerQueue = NULL;
HANDLE hNewTimer = NULL;
HANDLE hEvent = NULL;
HANDLE hHeap = NULL;
PVOID pNtContinue = GetProcAddress(GetModuleHandleA("Ntdll"), "NtContinue");
hTimerQueue = CreateTimerQueue();
hEvent = CreateEventW(0, 0, 0, 0);
PVOID pCopyOfStack = NULL;
void* pChildSP = NULL;
void* pRsp = NULL;
// [1] Create a buffer to back up current state of stack.
hHeap = GetProcessHeap();
pCopyOfStack = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, targetThreadToSpoof.totalRequiredStackSize);
// [2] Work out Child-SP of current frame.
pChildSP = GetChildSP();
// [3] Calculate Rsp at the point when NtWaitForSingleObject sys call
// is invoked so we know *where* to overwrite in memory.
// Subtract from current Child-SP stack size of KERNELBASE!WaitForSingleObject + NtWaitForSingleObject (0x8).
pRsp = (PCHAR)pChildSP - spoofedCallStack.front().totalStackSize - 0x8;
//std::cout << "[+] Child-SP of current frame: 0x" << std::hex << pChildSP << "\n";
//std::cout << "[+] Value of Rsp when NtWaitForSingleObject syscall is invoked: 0x" << std::hex << pRsp << "\n";
// [4] Set up timers.
if (CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)RtlCaptureContext, &ctxThread, 0, 0, WT_EXECUTEINTIMERTHREAD))
{
WaitForSingleObject(hEvent, 0x32);
memcpy(&ropBackUpStack, &ctxThread, sizeof(CONTEXT));
memcpy(&ropSpoofStack, &ctxThread, sizeof(CONTEXT));
memcpy(&ropRestoreStack, &ctxThread, sizeof(CONTEXT));
memcpy(&ropSetEvent, &ctxThread, sizeof(CONTEXT));
// Back up the stack.
// NB This PoC uses VCRUNTIME140!memcpy but a native equivalent exported by ntdll is RtlCopyMemoryNonTemporal.
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlcopymemorynontemporal
ropBackUpStack.Rsp -= 8;
ropBackUpStack.Rip = (DWORD64)memcpy; // VCRUNTIME140!memcpy
ropBackUpStack.Rcx = (DWORD64)pCopyOfStack; // Destination
ropBackUpStack.Rdx = (DWORD64)pRsp; // Source
ropBackUpStack.R8 = (DWORD64)targetThreadToSpoof.totalRequiredStackSize; // Length
// Overwrite the stack with fake callstack.
ropSpoofStack.Rsp -= 8;
ropSpoofStack.Rip = (DWORD64)memcpy;
ropSpoofStack.Rcx = (DWORD64)pRsp; // Destination
ropSpoofStack.Rdx = (DWORD64)targetThreadToSpoof.pFakeStackBuffer; // Source
ropSpoofStack.R8 = (DWORD64)targetThreadToSpoof.totalRequiredStackSize; // Length
// Restore original call stack.
ropRestoreStack.Rsp -= 8;
ropRestoreStack.Rip = (DWORD64)memcpy;
ropRestoreStack.Rcx = (DWORD64)pRsp; // Destination
ropRestoreStack.Rdx = (DWORD64)pCopyOfStack; // Source
ropRestoreStack.R8 = (DWORD64)targetThreadToSpoof.totalRequiredStackSize; // Length
// Set event.
ropSetEvent.Rsp -= 8;
ropSetEvent.Rip = (DWORD64)SetEvent;
ropSetEvent.Rcx = (DWORD64)hEvent;
std::cout << "[+] Masking call stack of main thread...\n";
// The timings here could be modified as there is a small window when call stack is unmasked.
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)pNtContinue, &ropBackUpStack, 1, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)pNtContinue, &ropSpoofStack, 10, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)pNtContinue, &ropRestoreStack, SleepTime, 0, WT_EXECUTEINTIMERTHREAD);
CreateTimerQueueTimer(&hNewTimer, hTimerQueue, (WAITORTIMERCALLBACK)pNtContinue, &ropSetEvent, SleepTime + 10, 0, WT_EXECUTEINTIMERTHREAD);
}
// [5] Wait for event to be set by timer. Call stack will be masked throughout this period.
WaitForSingleObject(hEvent, INFINITE);
// [6] Clean up.
std::cout << "[+] Call stack currently unmasked...\n";
DeleteTimerQueue(hTimerQueue);
HeapFree(hHeap, 0, pCopyOfStack);
}
void go()
{
do
MaskCallStack(15000);
while (TRUE);
}
int main(int argc, char* argv[])
{
std::cout << "[+] Dynamic call stack spoofer via timers by @joehowwolf. Based on Ekko Sleep Obfuscation by C5pider.\n";
std::cout << "[!] Currently only supports waits of WaitReason: UserRequest via KERNELBASE!WaitForSingleObjectEx.\n";
BOOL bStaticCallStack = true;
PVOID startAddr = 0;
HANDLE hThread = INVALID_HANDLE_VALUE;
DWORD dwThreadId = 0;
CONTEXT ctx = { 0 };
// [0] Determine if the stack mask is to be static or dynamically spoofed.
if (!NT_SUCCESS(HandleArgs(argc, argv, bStaticCallStack)))
{
return -1;
}
// [1] Initialise spoofed call stack.
if (bStaticCallStack)
{
// Create a fake call stack layout in memory from static struct.
std::cout << "[+] STATIC MODE: Initialising static call stack to spoof...\n";
if (!NT_SUCCESS(InitialiseStaticCallStackSpoofing(spoofedCallStack, targetThreadToSpoof)))
{
std::cout << "[-] Failed to initialise fake static call stack\n";
return -1;
}
// Set thread start address to localspl.dll!InitializePrintMonitor2 + 0xb20.
startAddr = (PCHAR)(GetProcAddress(GetModuleHandleA("localspl"), "InitializePrintMonitor2")) + 0xb20;
if (NULL == startAddr)
{
return -1;
}
}
else
{
// Create a fake call stack by finding a thread in the desired state (e.g. wait:UserRequest)
// and record its start address. Do this upfront because we *need* to know the start
// address in order to spoof it now.
std::cout << "[+] DYNAMIC MODE: Finding a suitable thread call stack to spoof...\n";
if (!NT_SUCCESS(InitialiseDynamicCallStackSpoofing(UserRequest, targetThreadToSpoof)))
{
std::cout << "[-] Failed to initialise dynamic static call stack\n";
return -1;
}
startAddr = targetThreadToSpoof.startAddr;
}
// [2] Start thread at fake start address.
std::cout << "[+] Spawning new thread at spoofed start address: 0x" << std::hex << startAddr << "\n";
hThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)startAddr,
0,
CREATE_SUSPENDED,
&dwThreadId);
ctx.ContextFlags = CONTEXT_CONTROL;
GetThreadContext(hThread, &ctx);
ctx.Rip = (DWORD64)&go;
SetThreadContext(hThread, &ctx);
// [3] Resume thread.
ResumeThread(hThread);
CloseHandle(hThread);
// [4] Exit current thread.
ExitThread(0);
}
+140
View File
@@ -0,0 +1,140 @@
<?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>{041bc7b3-b050-48fa-b587-68172c9e0597}</ProjectGuid>
<RootNamespace>CallStackMasker</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>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</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" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</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>
</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>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</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>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="CallStackMasker.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="CallStackUtils.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="CallStackMasker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="CallStackUtils.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff