Overview
In this blog, I explore the process of creating and injecting a DLL (Dynamic-Link Library) into a Windows application. The journey started with building a simple DLL in C++ that displays a message box upon being loaded into a target process. I then used a custom DLL injector to inject this DLL into a running application, confirming its successful execution. Along the way, I tackled challenges such as matching architectures (x86 vs. x64), setting up Visual Studio properly, and debugging issues. This step-by-step process serves as an introduction to DLL injection and provides a hands-on experience for beginners.
“What’s DLL?”
A DLL (Dynamic-Link Library) is a file that contains reusable code and data that multiple programs can use simultaneously in a Windows operating system. In this tutorial, we are using a Dynamic-Link Library (DLL) to inject custom functionality into a target. So, first we are going to create a custom DLL and then it will serve as a payload that gets injected into another process. Upon injection, the DLL executes custom code, such as displaying a message box or modifying the behavior of the target application.
Writing a DLL using visual Studio
- Create a DLL project using visual studio.
#include <windows.h>
#include <iostream>
#include "pch.h"
// A function to display a message box
extern "C" __declspec(dllexport) void ShowMessage() {
MessageBoxA(NULL, "DLL Injected Successfully!", "DLL Message", MB_OK | MB_ICONINFORMATION);
}
DWORD WINAPI MonitorKeys(LPVOID lpParam) {
while (true) {
// Check if the Page Up key is pressed
if (GetAsyncKeyState(VK_PRIOR) & 0x8000) {
ShowMessage();
Sleep(300);
}
}
return 0;
}
// The entry point for the DLL
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// Code to run when the DLL is loaded
MessageBoxA(NULL, "DLL Loaded!", "Info", MB_OK | MB_ICONINFORMATION);
CreateThread(NULL, 0, MonitorKeys, NULL, 0, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
I added a MessageBox
inside the DllMain
function to indicate when the DLL has been successfully loaded into the target process. This functionality is handled during the DLL_PROCESS_ATTACH
event, which is triggered automatically by the operating system when the DLL is loaded.
Additionally, I created an exported function, ShowMessage
, that can be explicitly called by the process after the DLL is loaded. This function serves as a simple example of how to interact with the DLL beyond its initial loading phase.
Together, these components demonstrate how to initialize and interact with a DLL both during the injection process and afterward. Once the code is compiled, it generates the DLL file, which is ready to be injected into a target process for demonstration or further use.
Writing the injector to the process
// DllInjector.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <Windows.h>
#include <TlHelp32.h>
DWORD GetProcId(const char* procName) {
DWORD procId = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry);
if (Process32First(hSnap, &procEntry)) {
do
{
if (!_stricmp(procEntry.szExeFile, procName))
{
procId = procEntry.th32ProcessID;
}
} while (Process32Next(hSnap, &procEntry));
}
CloseHandle(hSnap);
return procId;
}
}
int main()
{
std::cout << "Hello World!\n";
const char* dllPath = "C:\\Users\\test\\Desktop\\TestingDll.dll";
const char* procName = "notepad++.exe";
DWORD procId = 0;
while (!procId)
{
procId = GetProcId(procName);
Sleep(30);
}
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, 0, procId);
if (hProc && hProc != INVALID_HANDLE_VALUE) {
void* loc = VirtualAllocEx(hProc, 0, MAX_PATH, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(hProc, loc, dllPath, strlen(dllPath) + 1, 0);
HANDLE hThread = CreateRemoteThread(hProc, 0, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, loc, 0, 0);
if (hThread)
{
CloseHandle(hThread);
}
if (hProc)
{
CloseHandle(hProc);
}
return 0;
}
}
Code Walkthrough
GetProcId:
The GetProcId
function loops through all processes running on the system and retrieves the process ID (PID) of the target process by matching its name (e.g., notepad.exe
or ac_client.exe
).
Main Function:
dllPath
: We specify the path to the DLL file created earlier.procName
: The name of the process we want to inject the DLL into.
OpenProcess:
We retrieve the process ID (PID) using GetProcId
.
Then, we use OpenProcess to open the target process with the necessary permissions (PROCESS_ALL_ACCESS
) to allow:
- Memory allocation
- Thread creation
VirtualAllocEx:
Next, we use VirtualAllocEx to allocate memory in the virtual address space of the target process.
This step is necessary to reserve space in the process’s memory for the DLL path, which we’ll inject next.
WriteProcessMemory:
We use WriteProcessMemory to write the DLL path into the allocated memory in the target process.
This prepares the process to load the DLL.
CreateRemoteThread:
Finally, we use the CreateRemoteThread function to execute the LoadLibraryA
function inside the target process.
Why Create a Remote Thread?
CreateRemoteThread
creates a new thread in the target process, which runs independently of the process’s existing threads.LoadLibraryA
takes the DLL path (in the allocated memory) as an argument and loads the DLL into the target process.- Once loaded, the operating system automatically triggers the
DllMain
function within the DLL. - This avoids disrupting the normal execution flow of the target application while ensuring that our DLL is loaded and executed.
Once the DLL is injected, you gain access to the process’s memory and functions. This allows you to:
Modify game variables: For example, changing health, ammo, or scores by locating and editing their memory addresses.
In this demonstration, pressing the Page Up key triggers the ShowMessage()
function, which displays a confirmation message to verify the interaction between the DLL and the process.