Introduction

I found my first security vulnerability, a DLL hijacking flaw in CactusViewer v2.3.0, a lightweight Windows image viewer. The application loads several DLLs from its own directory before checking the Windows system directories, which means an attacker can plant a malicious DLL alongside the executable and get arbitrary code execution when the user launches it.

The CVE ID is currently pending from MITRE. The full advisory and proof of concept are available on my GitHub.

What is DLL Hijacking?

When a Windows application calls LoadLibrary to load a DLL, the system follows a specific search order:

  1. The directory from which the application loaded
  2. The system directory (C:\Windows\System32)
  3. The 16-bit system directory
  4. The Windows directory
  5. The current directory
  6. Directories listed in the PATH environment variable

If an application tries to load a DLL that doesn’t exist in its own directory, Windows returns NAME NOT FOUND and moves to the next location in the search order. But if an attacker places a malicious DLL with the expected name in the application’s directory, Windows loads it before ever reaching the legitimate system copy.

Windows has a protection mechanism called KnownDLLs, a registry key at HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs that forces certain critical DLLs to always load from System32. But not every DLL is listed there, and that’s where the opportunity lies.

Target Selection

I was looking for small, open-source Windows desktop applications that met a few criteria:

  • Written in C/C++ (more likely to use dynamic DLL loading)
  • Actively maintained (so a CVE would be relevant)
  • Distributed as a portable/standalone executable (users run it from user-writable directories like Desktop or Downloads)
  • Popular enough to matter, but small enough to be under-audited

CactusViewer fit perfectly. It’s a standalone image viewer built with Direct3D 11 and FreeType, distributed as a single .exe file with around 300 stars on GitHub.

Discovery Process

Step 1: Process Monitor Setup

The primary tool for discovering DLL hijacking vulnerabilities is Sysinternals Process Monitor. I set up the following filters:

  • Process Name is CactusViewer.exe → Include
  • Path ends with .dll → Include
  • Result is NAME NOT FOUND → Include

These filters isolate DLL load attempts that fail from the application’s directory, which is exactly what we need.

Step 2: Identify Missing DLLs

After launching CactusViewer with ProcMon running, I found 6 DLLs that the application attempted to load from its own directory before falling back to system directories:

Process Monitor showing 6 missing DLLs from the application directory

DLL Description
D3DCOMPILER_47.dll Direct3D Shader Compiler
d3d11.dll Direct3D 11 Runtime
dxgi.dll DirectX Graphics Infrastructure
CRYPTSP.dll Cryptographic Service Provider
d3d10warp.dll Direct3D 10 Software Rasterizer
Wldp.dll Windows Lockdown Policy

Step 3: Check KnownDLLs

Before building a PoC, I needed to verify that none of these DLLs are protected by the KnownDLLs mechanism:

reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"

None of the 6 DLLs appeared in the list. This confirms they’re all vulnerable to hijacking.

KnownDLLs registry showing affected DLLs are not protected

Step 4: Build the Proof of Concept

I wrote a minimal DLL that displays a MessageBox when loaded, proving arbitrary code execution:

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL, "DLL Hijack PoC - Arbitrary Code Execution",
                    "CactusViewer DLL Hijack", MB_OK);
        break;
    }
    return TRUE;
}

__declspec(dllexport) void D3DCompile(void) {
    return;
}

The D3DCompile export is a dummy function since some applications check for specific exports before accepting a DLL. I compiled it on Kali using MinGW:

x86_64-w64-mingw32-gcc poc.c -shared -o D3DCOMPILER_47.dll

Step 5: Confirm Exploitation

I placed the compiled D3DCOMPILER_47.dll in the same directory as CactusViewer.exe and launched the application. The MessageBox appeared immediately, confirming arbitrary code execution before the application even finished loading.

Malicious DLL placed alongside the application

MessageBox confirming arbitrary code execution

Going back to ProcMon with the Result is NAME NOT FOUND filter removed, I could see the application now loading my malicious DLL with a SUCCESS result instead of falling through to the system directory.

Process Monitor confirming successful load of the malicious DLL

Why This Matters

DLL hijacking might sound like a low-severity issue since the attacker needs to place a file on disk. But consider the realistic attack scenario for a portable application like CactusViewer:

  1. Attacker creates a ZIP archive containing CactusViewer.exe and D3DCOMPILER_47.dll
  2. The archive is distributed via phishing, torrent, file sharing, or a compromised download mirror
  3. User extracts the archive and runs CactusViewer.exe
  4. The malicious DLL executes automatically with no additional user interaction

This is especially effective because portable applications are designed to run from any directory. Users expect to extract and run them from their Downloads folder or Desktop, which are user-writable locations.

The CVSS v3.1 score is 7.8 (High): CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

Remediation

The fix is straightforward. The application should restrict DLL loading to the system directory using any of these approaches:

// Option 1: Restrict specific loads
LoadLibraryEx("d3d11.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);

// Option 2: Remove current directory from search order at startup
SetDllDirectory("");

// Option 3: Set default search path globally
SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_SYSTEM32);

Disclosure Timeline

Date Event
2026-02-27 Vulnerability discovered
2026-02-27 Vendor notified via GitHub Issue #65
2026-02-27 CVE requested from MITRE
TBD CVE ID assigned
TBD Vendor response

Methodology for Finding More

This same process can be repeated against any portable Windows application:

  1. Download the target app to a Windows VM
  2. Run Process Monitor with the filters above
  3. Launch the app and identify missing DLLs from the app directory
  4. Verify DLLs aren’t in KnownDLLs
  5. Build a PoC DLL with a MessageBox in DllMain
  6. Compile with MinGW and place alongside the executable
  7. Confirm execution, document everything, report to the vendor

The tools required are minimal: Sysinternals ProcMon, a Windows VM, and MinGW on your attack box (sudo apt install gcc-mingw-w64).

References