Skeleton Code to Create PoC DLL
In this page, you can find skeleton code to generate a DLL that creates a file in the %LOCALAPPDATA%\Temp directory. It can be compiled in Visual Studio. The generated DLL is a proof of concept, useful as a canary to aid security research. For example, if you want to test DLL hijacking, DLL side-loading, reflective DLL injection and similar techniques.
In this page:
DLL skeleton code
The code listed below can be compiled with Microsoft Visual Studio to generate a DLL. As soon as the DLL is loaded (DLL_PROCESS_ATTACH), the function TestFunction runs and creates an empty file in user’s temp directory.
The DLL also exports the function ExportFunction that can be called with or without an argument. The argument is the name of the file the TestFunction creates in the user’s temp directory (%LOCALAPPDATA%\Temp).
To accept an input argument, the exported function should be defined as VOID WINAPI ExportedFunction(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, INT nCmdShow). The parameter lpszCmdLine contains the arguments passed via rundll32.exe. To parse these arguments we use the Windows API functions MultiByteToWideChar and CommandLineToArgvW.
#include <windows.h>
#include <stdio.h>
BOOL TestFunction(LPWSTR fname, LPWSTR msgbuf)
{
// create a logfile in user's temp
// this file will be used as proof of execution
WCHAR lpBuffer[MAX_PATH] = {0};
DWORD len = ::GetTempPathW(MAX_PATH, lpBuffer);
if (!len)
{
WCHAR msgbuf[50];
swprintf_s(msgbuf, 50, L"[-] GetTempPathW has failed: %d", GetLastError());
//::OutputDebugString(msgbuf);
return FALSE;
}
size_t numberofelements = wcslen(lpBuffer) + wcslen(fname) + 1;
WCHAR* logfilepath = new WCHAR[numberofelements];
wcscpy_s(logfilepath, numberofelements, lpBuffer);
wcscat_s(logfilepath, numberofelements, fname);
HANDLE hFile = ::CreateFileW(
logfilepath,
GENERIC_ALL,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
WCHAR msgbuf[50];
swprintf_s(msgbuf, 50, L"[-] CreateFileW has failed: %d", GetLastError());
//::OutputDebugString(msgbuf);
return FALSE;
}
DWORD NumberOfBytesWritten;
WriteFile(hFile, (LPCVOID)msgbuf, wcslen(msgbuf) * sizeof(WCHAR), &NumberOfBytesWritten, NULL);
CloseHandle(hFile);
return TRUE;
}
VOID WINAPI ExportedFunction(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, INT nCmdShow) {
WCHAR lpWideCharStr[MAX_PATH] = { 0 };
INT wideLen = MultiByteToWideChar(CP_ACP, 0, lpszCmdLine, -1, lpWideCharStr, 0);
if (wideLen == 0) {
return;
}
wideLen = MultiByteToWideChar(CP_ACP, 0, lpszCmdLine, -1, lpWideCharStr, wideLen);
if (wideLen == 0) {
return;
}
WCHAR defaultname[] = L"filetest_exportedfunction.tmp";
INT argc = 0;
LPWSTR* argv = (wideLen > 1) ? CommandLineToArgvW(lpWideCharStr, &argc) : nullptr;
LPWSTR fname = (argc > 0) ? argv[0] : defaultname;
WCHAR msg[] = L"file content";
TestFunction(fname, msg);
return;
}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
WCHAR fname[] = L"filetest_dllmain.tmp";
WCHAR msg[] = L"DLL_PROCESS_ATTACH";
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
TestFunction(fname, msg);
break;
case DLL_THREAD_ATTACH:
case DLL_PROCESS_DETACH:
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
In general, you can execute a DLL using rundll32.exe on a Windows system:
rundll32.exe <DLL_name>,<exported_function_name>
To view the exported functions, dumpbin.exe - a tool that is part of Visual Studio - can be used:
dumpbin.exe /EXPORTS <name of the dll>
We can also call the exported function using the ordinal name. In this case, the ordinal of the exported function is 1 and thus the call would be:
rundll32.exe calc.dll,ExportedFunction
Build and use your own DLL launcher
When you execute the DLL with rundll32.exe, you are not able to view any output messages. For example, if you are using printf() in the DLL there is no output. We can deal with this by building our own launcher. The code listed below once compiled, creates an executable that gets as input a DLL, loads it and executes it.
#include <windows.h>
#include <stdio.h>
#include <iostream>
int main(int argc, char** argv)
{
printf("[+] Loading DLL...\n");
CHAR* dllname = argv[1];
HMODULE hLibrary = LoadLibraryA(dllname);
if (hLibrary == NULL)
{
printf("[-] LoadLibraryA has failed: %d\n", GetLastError());
return 1;
}
printf("[+] Handle of the loaded DLL: 0x%p\n", hLibrary);
FARPROC gpa = GetProcAddress(hLibrary, MAKEINTRESOURCEA(1));
if (gpa)
{
printf("[+] Module Address: 0x%p\n", gpa);
}
else
{
printf("[-] Last Error: %d\n", GetLastError());
return 1;
}
if (FreeLibrary(hLibrary))
{
printf("[+] Library has been unloaded successfuly!\n");
}
return 0;
}
Tools used
The tools used for this post are:
- Visual Studio
- CFF Explorer
- rundll32.exe
- dumpbin.exe